- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
本文聚焦仓颉标准库 std.io 包中的BufferedOutputStream<T>缓冲输出流,系统讲解其类声明、三类构造函数、write/writeByte/flush/reset核心写入 API,以及Resource与Seekable两个拓展接口的使用方式。读完本文,你将掌握如何为任意OutputStream类型(如ByteBuffer)叠加缓冲层以提升批量写入效率,并能正确地在try-with-resource与随机定位场景下使用该流。
一、BufferedOutputStream 在 std.io 中的定位
std.io包为仓颉程序与外部设备的数据交换提供了流式 I/O 的通用接口与具体实现:输入输出流类似一个数据通道,承载一段有序数据,程序从输入流读取数据、往输出流写入数据。在 类列表 中,BufferedOutputStream与BufferedInputStream、ByteBuffer、MultiOutputStream等共同构成了流式 I/O 的缓冲区与组合能力。
BufferedOutputStream<T>提供带缓冲区的输出流:可将其他OutputStream类型的输出流(如ByteBuffer)绑定到BufferedOutputStream实例。向该实例写入数据时,数据先进入内部缓冲区暂存,再由缓冲区写入被绑定的输出流,从而把多次小规模写入合并为更少的底层写入调用,减少与底层目标的交互次数。
public class BufferedOutputStream<T> <: OutputStream where T <: OutputStream { public init(output: T) public init(output: T, buffer: Array<Byte>) public init(output: T, capacity: Int64) }其泛型约束where T <: OutputStream意味着只能包装实现了 OutputStream 接口的类型。OutputStream接口本身只要求实现write(buffer: Array<Byte>)与flush()两个方法(flush提供默认为空的实现),因此任何符合该接口的自定义输出流(文件流、网络流、ByteBuffer等)都能被缓冲层包装。从设计上看,这是一种典型的装饰器(Decorator)模式:BufferedOutputStream在保持OutputStream外观不变的前提下,透明地为底层流附加缓冲能力。
二、三个构造函数:默认容量、复用缓冲区与显式容量
BufferedOutputStream提供三个重载构造函数,区别在于内部缓冲区的来源与容量大小。
| 构造函数原型 | 说明 |
|---|---|
init(output: T) | 创建实例,缓冲区容量取默认值4096字节。 |
init(output: T, buffer: Array<Byte>) | 内部使用的缓冲区由入参决定;在注重性能的场景下,通过复用传入的buffer可减少内存分配次数。 |
init(output: T, capacity: Int64) | 按显式容量创建内部缓冲区。 |
2.1 默认构造:开箱即用
import std.io.ByteBuffer import std.io.BufferedOutputStream import std.io.readToEnd main(): Unit { let outputStream = ByteBuffer() /* 绑定指定输出流 */ let bufferedStream = BufferedOutputStream(outputStream) /* 将输出数据写入缓冲流 bufferedStream 并刷新内部绑定的输出流 outputStream */ let outputData = "Hello World".toArray() bufferedStream.write(outputData) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream))) }运行结果:
Hello World这段代码演示了最基本的使用链路:ByteBuffer作为底层输出流,BufferedOutputStream绑定其上,写入后必须调用flush(),缓冲数据才会真正落到outputStream中,最后用 readToEnd 读取底层流中全部数据。
2.2 复用缓冲区构造:减少内存分配
init(output: T, buffer: Array<Byte>)该构造函数让调用方决定内部缓冲区的载体。若你持有长期存活的Array<Byte>,将其传入后,BufferedOutputStream直接复用它作为暂存区,从而避免每次创建实例时重新分配内存,在高频创建/销毁缓冲流的场景下可明显减少分配开销。适用前提是传入的buffer非空,否则按 IllegalArgumentException 处理:
import std.io.ByteBuffer import std.io.BufferedOutputStream main(): Unit { let outputStream = ByteBuffer() /* 使用合法的内部缓冲区创建 BufferedOutputStream 实例,不会抛出异常 */ try { let buffer = Array<Byte>(1024, repeat: 0) let bufferedStream = BufferedOutputStream(outputStream, buffer) } catch (e: IllegalArgumentException) { println("Error: ${e.message}") } /* 内部缓冲区大小被定义为0的情况 */ try { let invalidBuffer = Array<Byte>() let bufferedStream = BufferedOutputStream(outputStream, invalidBuffer) } catch (e: IllegalArgumentException) { println("Error: ${e.message}") } }运行结果:
Error: The buffer cannot be empty.2.3 显式容量构造:控制缓冲区规模
init(output: T, capacity: Int64)capacity为内部缓冲区容量。当capacity小于等于 0 时抛出IllegalArgumentException,错误信息形如Invalid capacity size: capacity = 0.:
import std.io.ByteBuffer import std.io.BufferedOutputStream main(): Unit { let outputStream = ByteBuffer() /* 使用合法的内部缓冲区容量创建 BufferedoutputStream 实例,不会抛出异常 */ try { let capacity = 2048 let bufferedStream = BufferedOutputStream(outputStream, capacity) } catch (e: IllegalArgumentException) { println("Error: ${e.message}") } /* 当内部缓冲区被设置成 0 时,抛出异常 */ try { let zeroCapacity = 0 let bufferedStream = BufferedOutputStream(outputStream, zeroCapacity) } catch (e: IllegalArgumentException) { println("Error: ${e.message}") } /* 当内部缓冲区被设置成负数时,抛出异常 */ try { let negativeCapacity = -1024 let bufferedStream = BufferedOutputStream(outputStream, negativeCapacity) } catch (e: IllegalArgumentException) { println("Error: ${e.message}") } }运行结果:
Error: Invalid capacity size: capacity = 0. Error: Invalid capacity size: capacity = -1024.建议:若单次写入数据量较大或写入频率较高,可将容量设置为略大于单批数据大小的值,减少缓冲区写满触发的下沉次数;若内存敏感,则选用较小的容量。
三、核心写入 API:write、writeByte、flush 与 reset
BufferedOutputStream的成员函数围绕"写入 → 暂存 → 刷新下沉"的缓冲语义展开。
| 名称 | 原型 | 说明 |
|---|---|---|
| write | (buffer: Array<Byte>) -> Unit | 将buffer中的数据写入到绑定的输出流中。 |
| writeByte | (v: Byte) -> Unit | 写入一个字节到绑定的输出流中。 |
| flush | () -> Unit | 刷新:将内部缓冲区的剩余数据写入绑定的输出流,并刷新BufferedOutputStream。 |
| reset | (output: T) -> Unit | 绑定新的输出流,重置状态,但不重置capacity。 |
3.1 write:批量写入并暂存于缓冲区
write(buffer: Array<Byte>)接收待写入数据的缓冲区。数据先进入BufferedOutputStream的内部缓冲区,而不是立即到达底层流,因此写入后必须配合flush()才能真正下沉数据(见 示例 中的完整流程)。
import std.io.ByteBuffer import std.io.BufferedOutputStream import std.io.readToEnd main(): Unit { let outputStream = ByteBuffer() let bufferedStream = BufferedOutputStream(outputStream) let outputData = "Hello World".toArray() bufferedStream.write(outputData) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream))) }3.2 writeByte:逐字节写入
writeByte(v: Byte)用于单字节写入。适合逐字节生成数据流的场景(如拼接编码后的字节序列),内部同样先写入缓冲区:
import std.io.ByteBuffer import std.io.BufferedOutputStream import std.io.readToEnd main(): Unit { let outputStream = ByteBuffer() let bufferedStream = BufferedOutputStream(outputStream) /* 将输出数据逐个写入缓冲流 bufferedStream 并刷新内部绑定的输出流 outputStream */ let outputData = "Hello World".toArray() for (byte in outputData) { bufferedStream.writeByte(byte) } bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream))) }运行结果:
Hello World3.3 flush:缓冲区数据下沉的关键操作
flush()将内部缓冲区的剩余数据写入绑定的输出流,并刷新BufferedOutputStream自身。在BufferedOutputStream的示例中可以看到缓冲语义的直观体现:
import std.io.* main(): Unit { let arr1 = "01234".toArray() let byteBuffer = ByteBuffer() byteBuffer.write(arr1) let bufferedOutputStream = BufferedOutputStream(byteBuffer) let arr2 = "56789".toArray() /* 向流中写入数据,此时数据在外部流的缓冲区中 */ bufferedOutputStream.write(arr2) /* 调用 flush 函数,真正将数据写入内部流中 */ bufferedOutputStream.flush() println(String.fromUtf8(readToEnd(byteBuffer))) }运行结果:
0123456789注意注释与结果:write(arr2)执行后数据仍暂存于缓冲层,只有flush()之后byteBuffer中才同时包含01234与56789,最终输出完整的0123456789。这提醒开发者:在读取底层流内容、关闭流或进行定位操作之前,务必先调用flush(),否则可能丢失缓冲区中尚未下沉的数据。
3.4 reset:切换绑定的输出流
reset(output: T)将新的输出流绑定到实例上,并重置内部状态(如清空缓冲区),但不会重置capacity——即重新绑定后缓冲区容量保持之前设定的值,避免重复分配。示例中先写入第一个输出流并刷新,再reset到第二个输出流继续写入:
import std.io.ByteBuffer import std.io.BufferedOutputStream import std.io.readToEnd main(): Unit { /* 创建第一个输出流 */ let outputStream1 = ByteBuffer() let sourceData1 = "First message: Hello".toArray() /* 创建第二个输出流 */ let outputStream2 = ByteBuffer() let sourceData2 = "Second message: World".toArray() /* 使用 BufferedOutputStream 包装第一个输出流 */ let bufferedStream = BufferedOutputStream(outputStream1) /* 将第一个源数据写入到绑定的第一个输出流中并刷新 */ bufferedStream.write(sourceData1) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream1))) /* 重置输出流为第二个输出流,将第二个源数据写入到绑定的第二个输出流中并刷新 */ bufferedStream.reset(outputStream2) bufferedStream.write(sourceData2) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream2))) }运行结果:
First message: Hello Second message: Worldreset适合复用同一个缓冲流实例依次向多个目标输出的场景,配合"不重置capacity"的特性,可在多目标写入时避免重复分配缓冲区。
四、拓展接口一:Resource——自动资源释放
BufferedOutputStream<T>在被绑定流也实现了 Resource 接口时(即T <: Resource),自动获得Resource能力,可在try-with-resource语法上下文中实现自动资源释放:
extend<T> BufferedOutputStream<T> <: Resource where T <: Resource| 成员 | 原型 | 说明 |
|---|---|---|
| close | () -> Unit | 关闭当前流。 |
| isClosed | () -> Bool | 判断当前流是否关闭,关闭返回true,否则返回false。 |
注意:调用
close()之后,不可再调用BufferedOutputStream的其他接口,否则会造成不可预期现象(如向已关闭的流继续写入或刷新)。
完整示例(自定义同时实现OutputStream与Resource的底层流):
import std.io.BufferedOutputStream import std.io.OutputStream import std.io.ByteBuffer import std.io.readToEnd /** * 自定义实现 OutputStream 和 Resource 接口的类A */ public class A <: OutputStream & Resource { private var closed: Bool = false public var outputStream = ByteBuffer() public func write(buffer: Array<Byte>): Unit { this.outputStream = ByteBuffer(buffer) } public func isClosed(): Bool { return closed } public func close(): Unit { println("Resource is closed") closed = true } } main(): Unit { let resourceStream = A() let bufferedStream = BufferedOutputStream(resourceStream) /* 使用try-with-resource语法获取资源 */ try (r = bufferedStream) { println("Get the resource") let data = "Hello World".toArray() r.write(data) r.flush() println(r.isClosed()) println(String.fromUtf8(readToEnd(resourceStream.outputStream))) } /* 自动调用 close() 函数释放资源 */ println(bufferedStream.isClosed()) }运行结果:
Get the resource false Hello World Resource is closed true可以看到:try (r = bufferedStream)块内isClosed()为false,块结束后自动调用close(),isClosed()变为true,无需手动释放。
五、拓展接口二:Seekable——长度查询与光标定位
当被绑定的输出流同时实现了 Seekable 接口时(T <: Seekable),BufferedOutputStream支持查询数据长度与移动光标:
extend<T> BufferedOutputStream<T> <: Seekable where T <: Seekable| 成员 | 原型 | 说明 |
|---|---|---|
| prop length | prop length: Int64 | 返回当前流中的总数据量。 |
| prop position | prop position: Int64 | 返回当前光标位置。 |
| prop remainLength | prop remainLength: Int64 | 返回当前流中未读的数据量。 |
| seek | (sp: SeekPosition) -> Int64 | 移动光标到指定位置,返回流中数据起点到移动后位置的偏移量(字节)。 |
5.1 SeekPosition 枚举:三种定位基准
seek的参数是 SeekPosition 枚举:
public enum SeekPosition { | Begin(Int64) // 从起点开始移动 | Current(Int64) // 从当前位置开始移动 | End(Int64) // 从末尾开始移动 }5.2 seek 的缓冲语义与边界规则
调用seek时有三个关键约定:
- 指定的位置不能位于流中数据头部之前,否则抛出 IOException,错误信息如
Can't move the position before the beginning of the stream.; - 指定位置可以超过流中数据末尾,属于合法操作;
- 调用
seek会先将缓冲区内的数据写到绑定的输出流里,再移动光标的位置——这与输入流的seek(先清空缓存区再移动)不同,输出流的seek会先下沉缓冲数据,确保定位前数据不丢失。
完整示例(自定义实现OutputStream与Seekable的底层流):
import std.io.BufferedOutputStream import std.io.OutputStream import std.io.ByteBuffer import std.io.Seekable import std.io.SeekPosition import std.io.IOException import std.io.readToEnd /** * 自定义实现 OutputStream 和 Seekable 接口的类A */ public class A <: OutputStream & Seekable { public var outputStream: ByteBuffer = ByteBuffer() public func write(buffer: Array<Byte>): Unit { this.outputStream = ByteBuffer(buffer) } public func seek(sp: SeekPosition): Int64 { return outputStream.seek(sp) } } main(): Unit { let seekableStream = A() let bufferedStream = BufferedOutputStream(seekableStream) let data = "Hello World".toArray() bufferedStream.write(data) bufferedStream.flush() /* 输出当前流中总数据量,当前光标位置,当前流中未读的数据量 */ println("Length : " + bufferedStream.length.toString()) println("Position : " + bufferedStream.position.toString()) println("Remain Length : " + bufferedStream.remainLength.toString()) /* 移动光标到指定位置,虽然超过了流中数据末尾但是合法的 */ println("Position after seek() : " + bufferedStream.seek(SeekPosition.Current(11)).toString()) /* 尝试移动到数据头部之前,抛出异常 */ try { bufferedStream.seek(SeekPosition.Begin(-1)) } catch (e: IOException) { println("Error: " + e.message) } /* 将光标移动到第一个单词之后,读取后续的数据 */ bufferedStream.seek(SeekPosition.Begin(6)) println(String.fromUtf8(readToEnd(seekableStream.outputStream))) }运行结果:
Length : 11 Position : 0 Remain Length : 11 Position after seek() : 11 Error: Can't move the position before the beginning of the stream. World本例展示了:写入并flush()后length为 11、position为 0、remainLength为 11;SeekPosition.Current(11)将光标移动到数据末尾(返回值 11,合法);SeekPosition.Begin(-1)越界抛出IOException;最后SeekPosition.Begin(6)定位到第一个单词之后,读取到剩余的World。
六、实战要点小结
- 先 flush 再读取/关闭:
write/writeByte写入的数据停留在内部缓冲区,读取底层流内容、close()或seek()之前需先flush(),否则可能丢失暂存数据。 - 复用缓冲区提升性能:高频创建缓冲流的场景优先使用
init(output, buffer)复用已分配的Array<Byte>,减少内存分配次数。 - 容量参数校验:
capacity必须大于 0,buffer必须非空,否则抛出IllegalArgumentException;reset重新绑定流时容量保持不变。 - 资源安全释放:当底层流实现
Resource时,用try (r = bufferedStream)包裹,块结束自动close();关闭后不得再调用其他接口。 - 随机定位:底层流实现
Seekable时可用length/position/remainLength查询状态,用seek(SeekPosition.Begin/Current/End(offset))定位;位置不得越过数据头部,但可越过数据末尾。
相关源码与文档索引:类 API 详情、OutputStream 接口、Seekable 接口、SeekPosition 枚举、Resource 接口、可运行示例、io 包概览。
- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
相关推荐
仓颉 std.io 实战:BufferedOutputStream 缓冲输出流的构造、写入与资源管理
仓颉 std.io 实战:BufferedOutputStream 缓冲输出流的构造、写入与资源管理 BufferedOutputStream<T 是仓颉标准库
人工智能AI 技能文档开发工具仓颉 std.io BufferedOutputStream 实战:带缓冲输出流的写入与 flush 刷新机制详解
仓颉 std.io BufferedOutputStream 实战:带缓冲输出流的写入与 flush 刷新机制详解 本文围绕仓颉标准库 std.io 中的 Bu
人工智能AI 技能文档开发工具仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证
仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证 BufferedOutputStream 是仓颉标准库 s
文档知识库教程OpenHarmony
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考