- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
BufferedOutputStream<T>是仓颉标准库 std.io 中提供带缓冲区的输出流实现,它包装任意OutputStream类型的底层输出流(如ByteBuffer),先将写入数据暂存于内部缓冲区,再成批地刷入目标流,从而减少底层 I/O 调用次数。本文将以 BufferedOutputStream 类文档 为主线,结合 io_package_classes.md、io_samples/buffered_output_stream.md 等仓库文档,完整讲解其三个构造函数、写入/刷新/重置等核心成员函数,以及Resource与Seekable两个扩展接口的使用方法,帮助读者掌握缓冲输出流在实际项目中的正确打开、写入、刷新与关闭方式。
一、类定位:什么是缓冲输出流
在 std.io 包概览 中,I/O 操作被描述为“程序与外部设备进行数据交换的操作”,输入输出流类似一条承载有序数据的数据通道:程序从输入流读取数据(来自文件、网络等),向输出流写入数据(通往文件、网络等)。BufferedOutputStream 类文档 将该类定位为“提供带缓冲区的输出流”:
可将其他
OutputStream类型的输入流(如ByteBuffer)绑定到BufferedOutputStream实例,从该实例写入数据时,先把数据写入缓冲区暂存,再从缓冲区写入数据到流中。
从 io_package_classes.md 可以看到完整的类声明:
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) }关键设计点:
- 泛型约束:
T <: OutputStream表示被绑定的底层输出流必须实现 OutputStream 接口,该接口要求实现write(buffer: Array<Byte>): Unit,并提供默认空实现的flush(): Unit。 - 父类型:
BufferedOutputStream<T>自身同样继承自OutputStream,因此它可以被嵌套包装,也可以传递给任何接受OutputStream的 API(例如 copy 函数)。 - 缓冲语义:写入的数据先进入内部缓冲区,只有调用
flush()时才会真正落入被绑定的底层流。这与 BufferedInputStream 形成对称的读/写缓冲结构。
二、构造函数:三种方式配置内部缓冲区
构造函数是使用BufferedOutputStream的第一步,类文档 给出了三种重载:
| 名称 | 原型 | 说明 |
|---|---|---|
| init | (output: T) | 创建实例,缓冲区容量取默认值 4096。 |
| init | (output: T, buffer: Array<Byte>) | 内部使用的缓冲区由入参决定;注重性能的场景下,通过复用传入的buffer可减少内存分配次数、提高性能。 |
| init | (output: T, capacity: Int64) | 按指定容量创建实例。 |
2.1 默认构造:init(output: T)
不指定缓冲区容量,内部缓冲区大小为默认值4096 字节。适用于大多数普通场景,无需关心内部细节:
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该示例可在 io_package_classes.md 的init(T)小节找到,其中readToEnd是 std.io 提供的读取函数,用于取走ByteBuffer中未被读取的全部数据。
2.2 复用缓冲区:init(output: T, buffer: Array<Byte>)
在需要反复创建缓冲流的高频场景(如循环写入大量小块数据),可以通过传入预先分配的Array<Byte>来复用内存,避免每次构造都重新分配缓冲区。仓库文档明确指出这是“注重性能的场景”下的推荐做法。
需要注意:当buffer大小等于 0 时,会抛出 IllegalArgumentException,对应错误信息为The buffer cannot be empty.:
import std.io.ByteBuffer import std.io.BufferedOutputStream main(): Unit { let outputStream = ByteBuffer() /* 使用合法的内部缓冲区创建实例,不会抛出异常 */ 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小于等于 0 时,同样抛出IllegalArgumentException:
import std.io.ByteBuffer import std.io.BufferedOutputStream main(): Unit { let outputStream = ByteBuffer() /* 使用合法的内部缓冲区容量创建实例,不会抛出异常 */ 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.三、成员函数:写入、刷新与重置
类文档 定义了 4 个成员函数:
| 名称 | 原型 | 说明 |
|---|---|---|
| flush | () -> Unit | 将内部缓冲区的剩余数据写入绑定的输出流,并刷新 BufferedOutputStream。 |
| reset | (output: T) -> Unit | 绑定新的输出流,重置状态,但不重置capacity。 |
| write | (buffer: Array<Byte>) -> Unit | 将buffer中的数据写入到绑定的输出流中。 |
| writeByte | (v: Byte) -> Unit | 写入一个字节到绑定的输出流中。 |
3.1 write:批量写入数据
write(buffer: Array<Byte>)接受一个字节数组作为待写入数据。注意其语义是“写入到绑定的输出流”,但数据实际先进入内部缓冲区暂存,并不会立刻到达底层流。典型用法如下(与 flush 配套):
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))) }3.2 writeByte:逐字节写入
对于需要逐字节构造数据的场景,writeByte(v: Byte)可一次写入一个字节。多次调用后缓冲区会积攒数据,最终仍需flush()落盘:
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”。在 io_samples/buffered_output_stream.md 的官方示例中,这一“先写缓冲区、再 flush 落盘”的过程有非常直观的注释:
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从底层看,OutputStream 接口 的flush()默认实现为空,而BufferedOutputStream重写了该语义——先冲刷自身内部缓冲区到被绑定的流,再调用底层流的flush(),因此任何继承自OutputStream的实现(如ByteBuffer、文件流等)都能获得一致的“先攒后写”行为。
3.4 reset:切换绑定的输出流
reset(output: T)允许为同一个缓冲对象绑定新的输出流,并重置内部状态,但不会重置capacity——即缓冲区容量在实例生命周期内保持稳定,适合需要复用一个缓冲流对象连续写多个目标的场景:
import std.io.ByteBuffer import std.io.BufferedOutputStream import std.io.IOException 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: World四、扩展接口:Resource 与自动资源释放
当被绑定的底层流同样实现了Resource接口时,BufferedOutputStream<T>会通过扩展接口BufferedOutputStream<T> <: Resource where T <: Resource获得资源管理能力,可在try-with-resource语法上下文中实现自动资源释放(详见 io_package_classes.md)。
扩展接口新增两个成员:
| 名称 | 原型 | 说明 |
|---|---|---|
| close | () -> Unit | 关闭当前流。注意:调用此方法后不可再调用 BufferedOutputStream 的其他接口,否则会造成不可期现象。 |
| isClosed | () -> Bool | 判断当前流是否关闭。 |
完整示例(自定义同时实现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-with-resource的执行时序:块内isClosed()为false;块正常结束时会自动调用close()释放资源(打印Resource is closed),块外再次检查isClosed()返回true。这意味着在实际项目中,用try (r = BufferedOutputStream(...)) { ... }包裹写入逻辑即可免去手动关闭的负担。
五、扩展接口:Seekable 与光标移动
当被绑定的底层流实现Seekable接口时,BufferedOutputStream<T> <: Seekable where T <: Seekable扩展使其具备查询数据长度、移动光标的能力,这与 Seekable 接口定义 保持一致:
| 名称 | 原型 | 说明 |
|---|---|---|
| prop length | Int64 | 返回当前流中的总数据量。 |
| prop position | Int64 | 返回当前光标位置。 |
| prop remainLength | Int64 | 返回当前流中未读的数据量。 |
| seek | (sp: SeekPosition) -> Int64 | 移动光标到指定的位置。 |
seek的使用有三个重要约定(来自类文档与 io_package_classes.md):
- 指定的位置不能位于流中数据头部之前,否则抛出 IOException(错误信息为
Can't move the position before the beginning of the stream.); - 指定位置可以超过流中数据末尾,属于合法操作;
- 调用该函数会先将缓存区内的数据写到绑定的输出流里,再移动光标的位置,保证底层流数据完整后再定位。
SeekPosition是 std.io 中的枚举类型,包含三种定位基准:
public enum SeekPosition { | Begin(Int64) // 从起点开始移动 | Current(Int64) // 从当前位置开始移动 | End(Int64) // 从末尾开始移动 }完整示例(自定义同时实现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()后,光标位置为 0、未读数据量为 11;seek(SeekPosition.Current(11))返回 11(起点到移动后位置的字节偏移);SeekPosition.Begin(-1)因越界抛出IOException;最后将光标移到偏移 6 处后,readToEnd读出剩余的World,说明光标移动已成功作用到底层流。
六、异常与使用注意事项总结
综合 类文档 与 io_package_classes.md 中的可验证信息,使用BufferedOutputStream时需特别注意:
- 缓冲区参数校验:
buffer大小等于 0 或capacity小于等于 0 时,构造均抛出IllegalArgumentException(对应The buffer cannot be empty.与Invalid capacity size: capacity = 0.等错误信息)。 - 关闭后不可再用:
close()之后调用其他接口会造成不可期现象,应通过isClosed()或try-with-resource结构规避。 - seek 越界:光标不允许移到流数据头部之前,否则抛出
IOException;但允许超过数据末尾。 - 缓冲数据不可见性:写入的数据在
flush()之前只存在于内部缓冲区,若中途不再写入,务必在读取侧验证前调用flush()。 - 能力取决于底层流:
Resource与Seekable扩展均是条件扩展(where T <: Resource/where T <: Seekable),只有当绑定的T实现对应接口时才可用,这也是选择底层流类型时需要预先考虑的因素。
七、小结
BufferedOutputStream<T>是仓颉标准库中实现“写缓冲”的核心类,通过三层结构支撑了日常 I/O 开发:
- 构造层:默认 4096 字节容量、复用外部缓冲区、显式指定容量三种方式,兼顾易用与性能;
- 功能层:
write/writeByte负责攒数据,flush负责落盘,reset支持复用对象切换目标流; - 能力层:条件扩展
Resource提供try-with-resource自动释放,条件扩展Seekable提供length/position/remainLength/seek随机访问能力。
官方示例 buffered_output_stream.md 以及 ByteBuffer 示例 中的write、seek、readToEnd、readString等用法,均可与本文内容组合使用;更多接口级细节可继续查阅 io_package_api/io_package_classes.md、io_package_api/io_package_interfaces.md 与 io_package_api/io_package_funcs.md。
- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
相关推荐
仓颉标准库 std.io 缓冲输出流 BufferedOutputStream 使用详解与源码剖析
仓颉标准库 std.io 缓冲输出流 BufferedOutputStream 使用详解与源码剖析 本篇技术指南以仓颉(Cangjie)标准库 std.io 中
编程语言语言运行时标准库垃圾回收并发编程仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证
仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证 BufferedOutputStream 是仓颉标准库 s
文档知识库教程OpenHarmony仓颉标准库 std.io 带缓冲输入流 BufferedInputStream 完全指南:构造、读取、随机访问与资源管理
仓颉标准库 std.io 带缓冲输入流 BufferedInputStream 完全指南:构造、读取、随机访问与资源管理 本篇技术指南围绕仓颉语言标准库 std
人工智能AI 技能文档开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考