- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
本篇技术指南以仓颉(Cangjie)标准库std.io中的BufferedOutputStream为核心,结合官方示例与 buffered_output_stream.cj 源码,系统讲解缓冲输出流的构造方式、写入与刷新机制、资源管理以及 Seekable 定位能力。读完本文,你将掌握如何用缓冲输出流包装任意OutputStream(如ByteBuffer)来减少底层 I/O 调用次数,并能准确理解write、flush、reset、close的底层行为与异常边界。
BufferedOutputStream 在 std.io 中的定位
仓颉std.io包提供程序与外部设备进行数据交换的能力,输入输出流类似一个数据通道,承载一段有序数据。BufferedOutputStream是其中提供带缓冲区输出流的标准类,其源码定义于 buffered_output_stream.cj:
public class BufferedOutputStream<T> <: OutputStream where T <: OutputStream { var outputStream: T var outBuf: Array<Byte> var curPos: Int64 }从类签名可以看出两点核心设计:
- 泛型绑定:
T <: OutputStream,即可以包装任意实现了OutputStream接口的类型(如ByteBuffer、自定义输出流等),底层接口定义见 stream.cj; - 缓冲语义:写入数据先进入内部缓冲区
outBuf暂存,达到一定条件后再整体写入绑定的输出流,从而减少对底层流的写入次数。
官方示例:向缓冲流写入并刷新
官方文档 buffered_output_stream.md 给出的完整示例如下:
import std.io.* main(): Unit { // 准备源数据并写入 ByteBuffer let sourceBytes = "01234".toArray() let byteBuffer = ByteBuffer() byteBuffer.write(sourceBytes) // 用缓冲输出流包装 ByteBuffer let bufferedOutputStream = BufferedOutputStream(byteBuffer) let appendBytes = "56789".toArray() // 向流中写入数据,此时数据暂存在缓冲区中 bufferedOutputStream.write(appendBytes) // 调用 flush 函数,将缓冲区数据真正写入内部流 bufferedOutputStream.flush() println("刷新后的完整数据: ${String.fromUtf8(readToEnd(byteBuffer))}") }运行结果:
刷新后的完整数据: 0123456789该示例的关键点在于:bufferedOutputStream.write(appendBytes)这一步只把 "56789" 写入内部缓冲区,底层byteBuffer此时仍只有 "01234";只有调用flush()之后,缓冲区中的 "56789" 才被真正写入byteBuffer,最终读出完整的 "0123456789"。这正是缓冲输出流"先缓存、后落盘"的工作方式。
构造方式:默认容量、自定义容量与复用缓冲区
BufferedOutputStream提供三种构造函数(见 buffered_output_stream.cj):
1.init(output: T)— 默认缓冲区
public init(output: T)使用默认缓冲区容量 4096 字节(常量DEFAULT_BUFFER_CAPACITY定义于 stream.cj)。适用于大多数常规场景,无需关心缓冲大小。
2.init(output: T, buffer: Array<Byte>)— 复用外部缓冲区
public init(output: T, buffer: Array<Byte>)内部缓冲区由入参buffer决定,在注重性能的场景下,通过复用传入的buffer可以减少内存分配次数、提高性能。源码校验:当buffer.size == 0时抛出IllegalArgumentException,异常信息为Invalid argument 'buffer': buffer size is 0.。
3.init(output: T, capacity: Int64)— 指定容量
public init(output: T, capacity: Int64)按capacity字节创建内部缓冲区(Array<Byte>(capacity, repeat: 0))。源码校验:当capacity <= 0时抛出IllegalArgumentException,异常信息为Invalid argument 'capacity': expected a positive value, got '${capacity}'.。注意与ByteBuffer构造的校验差异——ByteBuffer允许容量为 0(见 byte_buffer.cj),而BufferedOutputStream的缓冲容量必须为正数。
构造异常验证示例
以下代码完整演示两种非法构造参数及其异常输出(对应 io_package_classes.md 中init(T, Array<Byte>)与init(T, Int64)章节):
import std.io.ByteBuffer import std.io.BufferedOutputStream main(): Unit { let outputStream = ByteBuffer() // 空缓冲区作为内部缓冲区,抛出异常 try { let invalidBuffer = Array<Byte>() let bufferedStream = BufferedOutputStream(outputStream, invalidBuffer) } 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 argument 'buffer': buffer size is 0. Error: Invalid argument 'capacity': expected a positive value, got '0'. Error: Invalid argument 'capacity': expected a positive value, got '-1024'.写入机制:write / writeByte 与缓冲区刷新的触发条件
func write(buffer: Array<Byte>)
源码逻辑(buffered_output_stream.cj):
public func write(buffer: Array<Byte>): Unit { let len = buffer.size if (len == 0) { return } let outBufSize = outBuf.size if (len >= outBufSize) { flushOutBuf() outputStream.write(buffer) return } if (len > outBufSize - curPos) { flushOutBuf() } buffer.copyTo(outBuf, 0, curPos, len) curPos += len }写入时的三种分支值得仔细理解:
- 空数据直接返回:待写入长度为 0 时不产生任何操作;
- 大数据直写底层流:当待写入数据长度不小于缓冲区容量时,先把缓冲区已有的残留数据刷出(
flushOutBuf()),再将本次数据直接写入底层输出流,不再经过缓冲区——避免"大块数据先拷贝进小缓冲区、再立刻刷出"的无谓开销; - 小数据入缓冲:若缓冲区剩余空间不足(
len > outBufSize - curPos),先刷新缓冲区腾出空间,再把数据copyTo进缓冲区并推进curPos。
func writeByte(v: Byte)
逐字节写入(buffered_output_stream.cj):当缓冲区已满(curPos == outBufSize)时先刷新,再写入单个字节并curPos++。适合逐字节构造数据的场景,缓冲机制可大幅降低底层写入调用次数。
func flush()与私有flushOutBuf()
func flushOutBuf(): Unit { if (curPos > 0) { outputStream.write(outBuf.slice(0, curPos)) curPos = 0 } } public func flush(): Unit { flushOutBuf() outputStream.flush() }flushOutBuf()是私有方法:把缓冲区中[0, curPos)的有效数据写入底层流,并复位curPos;flush()是公开方法:先flushOutBuf(),再调用底层流的flush(),确保缓冲数据与底层流状态都被刷新。OutputStream接口的flush()默认实现为空(见 stream.cj),由具体实现决定是否执行更底层的刷新。
什么时候数据才会真正写入底层流?
综合源码可以归纳出四个触发刷新的时机:
- 调用
flush()或close()时; - 写入的数据长度不小于缓冲区容量时(直写路径);
- 缓冲区剩余空间不足以容纳本次写入时;
- 缓冲区已满而继续
writeByte时。
切换底层流:reset 方法
public func reset(output: T): Unit { flush() outputStream = output curPos = 0 }reset用于绑定新的输出流并重置状态,但不重置缓冲区容量(见 buffered_output_stream.cj)。调用顺序为:先flush()把旧缓冲区数据写入当前绑定的流,再切换到新流并复位curPos。官方示例验证(见 io_package_classes.md 中func reset(T)章节):
main(): Unit { let outputStream1 = ByteBuffer() let outputStream2 = ByteBuffer() // 包装第一个输出流 let bufferedStream = BufferedOutputStream(outputStream1) bufferedStream.write("First message: Hello".toArray()) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream1))) // First message: Hello // 重置为第二个输出流 bufferedStream.reset(outputStream2) bufferedStream.write("Second message: World".toArray()) bufferedStream.flush() println(String.fromUtf8(readToEnd(outputStream2))) // Second message: World }运行结果:
First message: Hello Second message: World资源管理:Resource 接口与 close
当被包装的流类型同时实现了Resource接口时,BufferedOutputStream也自动具备Resource能力(见 buffered_output_stream.cj):
extend<T> BufferedOutputStream<T> <: Resource where T <: Resource { public func close(): Unit { if (!isClosed()) { try { flush() } finally { outputStream.close() } } } public func isClosed(): Bool { outputStream.isClosed() } }close()的语义是:先flush()把缓冲区残留数据写入底层流,再关闭底层流;finally保证即使刷新抛出异常也会执行关闭动作。关闭后不应再调用BufferedOutputStream的其他接口,否则会造成非预期现象。具备Resource能力意味着该对象可以在try-with-resource语法上下文中实现自动资源释放。
定位能力:Seekable 接口扩展
当被包装的流实现了Seekable接口时,BufferedOutputStream同样支持光标定位与长度查询(见 buffered_output_stream.cj):
| 成员 | 功能 | 底层实现 |
|---|---|---|
func seek(sp: SeekPosition): Int64 | 移动光标到指定位置 | 先flush()再调用outputStream.seek(sp),返回数据起点到移动后位置的字节偏移 |
prop position: Int64 | 返回当前光标位置 | 委托outputStream.seek(Current(0)) |
prop remainLength: Int64 | 返回未读数据字节数 | 记录旧位置 → seek 到末尾 → 恢复旧位置 → 返回差值 |
prop length: Int64 | 返回流中总数据字节数 | 同上流程,返回末尾偏移 |
其中SeekPosition是std.io提供的枚举(定义于 io_utils.cj):
public enum SeekPosition { | Current(Int64) // 相对当前光标位置 | Begin(Int64) // 相对流数据起点 | End(Int64) // 相对流数据末尾 }seek有两个值得注意的约束(与接口文档一致):指定的位置不能位于流中数据头部之前(否则抛出IOException,异常信息为Failed to seek: position '...' is before the beginning of the stream.);指定的位置可以超过流中数据末尾(合法)。由于seek会先刷新缓冲区,定位操作不会丢失缓存中的数据。
官方示例(见 io_package_classes.md 中func seek(SeekPosition)章节)运行结果如下:
Length : 11 Position : 0 Remain Length : 11 Position after seek() : 11 Error: Failed to seek: position '-1' is before the beginning of the stream. World与 readToEnd / ByteBuffer 的配合
示例中多次出现的readToEnd是std.io提供的工具函数(定义于 io_utils.cj):
public func readToEnd<T>(from: T): Array<Byte> where T <: InputStream & Seekable它要求入参同时实现InputStream与Seekable,会读取入参中未被读取的全部数据并返回字节数组。而ByteBuffer同时实现了IOStream(即InputStream & OutputStream)与Seekable(见 byte_buffer.cj),因此既能作为BufferedOutputStream的底层输出目标,也能通过readToEnd把写入结果读回来验证——这正是官方示例能够"写入后再整体读出"的原因。
小结与注意事项
- 缓冲的目的:将多次小规模写入合并为对底层流的一次或少数几次写入,降低 I/O 开销;对于大数据块则绕过缓冲区直写,避免拷贝损耗;
- 必须 flush 才能落盘:缓冲数据只有通过
flush()(或触发自动刷新的条件)才会写入底层流,示例中"刷新前只有 01234、刷新后有 0123456789"的对比就是最直观的印证; - 构造参数合法性:缓冲区容量必须为正整数,空
Array<Byte>不可作为内部缓冲区,否则抛IllegalArgumentException; - 生命周期:
close()会先刷新再关闭底层流;关闭后继续使用会引发非预期现象,建议配合try-with-resource使用; - 定位行为:
seek前自动刷新,确保缓存数据不丢失;位置不能越过数据头部。
相关源码与文档的仓库相对路径汇总:
- 官方示例:stdlib/doc/libs/std/io/io_samples/buffered_output_stream.md
- 类实现源码:stdlib/libs/std/io/buffered_output_stream.cj
- 接口与常量定义:stdlib/libs/std/io/stream.cj
- 底层目标流 ByteBuffer:stdlib/libs/std/io/byte_buffer.cj
- 工具函数 readToEnd 与 SeekPosition:stdlib/libs/std/io/io_utils.cj
- 完整 API 文档(构造、flush、reset、write、writeByte、Resource、Seekable 各节):stdlib/doc/libs/std/io/io_package_api/io_package_classes.md
- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
相关推荐
仓颉 std.io BufferedInputStream 缓冲输入流实战:从读取示例到源码级缓冲机制剖析
仓颉 std.io BufferedInputStream 缓冲输入流实战:从读取示例到源码级缓冲机制剖析 本文以 stdlib/doc/libs/std/io
编程语言语言运行时标准库垃圾回收并发编程仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证
仓颉编程语言 BufferedOutputStream 实战指南:带缓冲输出流的写入、刷新与源码印证 BufferedOutputStream 是仓颉标准库 s
文档知识库教程OpenHarmony仓颉标准库 std.io 枚举 SeekPosition 详解:流式读写的光标定位机制
仓颉标准库 std.io 枚举 SeekPosition 详解:流式读写的光标定位机制 导读 SeekPosition 是仓颉标准库 std.io 包中用于描述
编程语言语言运行时标准库垃圾回收并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考