- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
本文以 stdlib/doc/libs/std/io/io_samples/buffered_input_stream.md 中的官方示例为骨架,结合std.io包源码,系统讲解仓颉(Cangjie)标准库BufferedInputStream<T>的构造方式、缓冲读取原理、配套工具函数与可复用能力。读完本文,你将掌握如何用BufferedInputStream包装任意InputStream(如ByteBuffer)实现高效批量读取,并能基于源码理解其内部缓冲区的填充与消费机制。
一、示例全景:用 BufferedInputStream 包装 ByteBuffer 并读取数据
官方示例演示了标准用法:准备一段字节数据写入ByteBuffer,再用BufferedInputStream包装它,最后通过read一次读入用户缓冲区。完整代码如下(原文继承):
import std.io.* main(): Unit { // 准备源数据并写入 ByteBuffer let sourceBytes = "0123456789".toArray() let byteBuffer = ByteBuffer() byteBuffer.write(sourceBytes) // 用缓冲输入流包装 ByteBuffer let bufferedInputStream = BufferedInputStream(byteBuffer) let readBuffer = Array<Byte>(20, repeat: 0) // 读取流中数据,返回读取到的数据长度 let readLen = bufferedInputStream.read(readBuffer) println("读取到 ${readLen} 个字节: ${String.fromUtf8(readBuffer[..readLen])}") }运行结果:
读取到 10 个字节: 0123456789这个示例虽短,却完整覆盖了三个关键环节,后文将逐一展开:
- 数据源:
"0123456789".toArray()把字符串转成字节数组,ByteBuffer作为内存中的字节流承载数据; - 包装:
BufferedInputStream(byteBuffer)将ByteBuffer绑定为内部输入流; - 读取:
read(readBuffer)返回实际读取字节数,用String.fromUtf8(readBuffer[..readLen])将读取到的字节段还原为字符串输出。
二、认识 BufferedInputStream:为任意输入流加一层缓冲区
BufferedInputStream是仓颉std.io包提供的带缓冲输入流类,源码定义见 stdlib/libs/std/io/buffered_input_stream.cj:
public class BufferedInputStream<T> <: InputStream where T <: InputStream { var inputStream: T let inBuf: Array<Byte> var curPos: Int64 = 0 var availLen: Int64 = 0 ... }其设计思路与主流语言(如 Java 的BufferedInputStream)一致:内部维护一块字节缓冲区inBuf,读取数据时先把底层流的数据成块搬入缓冲区,再按需供给用户,从而减少对底层流的调用次数。三个内部状态变量的含义分别是:
inputStream:被绑定的底层输入流(泛型T,要求实现InputStream接口);inBuf:内部缓冲区数组;curPos/availLen:当前缓冲区内的读取位置 / 尚未消费的有效字节数。
2.1 三种构造方式与参数校验
从 stdlib/libs/std/io/buffered_input_stream.cj 可见共有三个构造函数:
| 构造方式 | 说明 | 默认值/校验 |
|---|---|---|
init(input: T) | 绑定输入流,缓冲区容量取默认值 | 默认容量DEFAULT_BUFFER_CAPACITY = 4096(定义于 stdlib/libs/std/io/stream.cj) |
init(input: T, capacity: Int64) | 绑定输入流并指定缓冲区容量 | capacity <= 0时抛出IllegalArgumentException |
init(input: T, buffer: Array<Byte>) | 绑定输入流并复用调用方提供的数组作为缓冲区 | buffer.size == 0时抛出IllegalArgumentException |
第二种与第三种的区别值得注意:显式传入Array<Byte>时,内部直接复用该数组(零拷贝),在高频创建流对象、注重性能的场景下可以减少内存分配次数。官方 API 文档 stdlib/doc/libs/std/io/io_package_api/io_package_classes.md 对此有专门说明,并给出了容量参数的异常示例,非法入参对应的异常消息可在源码中精确复现:
Error: Invalid argument 'capacity': expected a positive value, got '0'. Error: Invalid argument 'capacity': expected a positive value, got '-1024'.2.2 InputStream 接口契约
BufferedInputStream的父类型是 InputStream,该接口只要求实现一个方法(见 stdlib/libs/std/io/stream.cj):
public interface InputStream { func read(buffer: Array<Byte>): Int64 }契约要点:read将数据读入buffer,返回读取的字节数(永不返回 0,除非流到达末尾);具体实现可能一次读满缓冲区,也可能只读当前可用数据,不保证读满buffer.size字节。这正是BufferedInputStream存在的意义——对上层隐藏底层流的这些不确定性。
三、源码级剖析:缓冲读取机制如何工作
3.1 read:批量读取的核心循环
read(buffer: Array<Byte>)的实现见 stdlib/libs/std/io/buffered_input_stream.cj,其核心是一个"取缓冲—搬数据"的循环:
- 流结束判定:若
availLen == -1(哨兵值,表示底层流已读完),直接返回 0; - 空缓冲区校验:用户传入的
buffer.size == 0时抛出IllegalArgumentException; - 循环填充:当
availLen == 0时调用私有方法fillInBuf()从底层流补充数据;若补充后仍为-1则退出循环; - 拷贝消费:每次把
min(availLen, len)字节从inBuf拷贝到用户缓冲区,更新curPos、availLen与累计计数,直至用户缓冲区填满或底层流耗尽。
其中fillInBuf()的实现在 stdlib/libs/std/io/buffered_input_stream.cj:
func fillInBuf(): Unit { let readNum = inputStream.read(inBuf) if (readNum > 0) { curPos = 0 availLen = readNum } else { availLen = -1 } }可以看到:底层流每被调用一次read,最多搬入inBuf容量(默认 4096 字节)的数据,之后多次read都从缓冲区直接消费,显著减少了对底层流的调用频次——这就是缓冲流的核心性能收益。
3.2 readByte:单字节读取
readByte(): ?Byte用于逐字节读取(见 stdlib/libs/std/io/buffered_input_stream.cj):缓冲区有数据时直接消费一个字节,缓冲区空则触发fillInBuf();流已结束返回None,否则返回?Byte包装的字节。官方文档示例用while (true)配合readByte()依次读出"abc"的每个字符,以byte == None作为循环终止条件:
while (true) { let byte = bufferedStream.readByte() if (byte == None) { break } println(String.fromUtf8(byte.getOrThrow())) }输出:
a b c3.3 底层 ByteBuffer 的读取语义
示例中底层流是ByteBuffer。它的read实现(见 stdlib/libs/std/io/byte_buffer.cj)遵循同样的"读多少给多少"语义:内部_length == 0时返回 0 表示流结束;否则取min(_length, buffer.size)拷贝到目标缓冲区并推进内部起始位置。因此示例中ByteBuffer里只有 10 字节数据时,BufferedInputStream一次fillInBuf读满内部缓冲区的 10 字节,再全部供给用户,read最终返回 10——与运行结果一致。
四、进阶能力:reset、资源管理与随机访问
4.1 reset:换绑新输入流而不换缓冲区
reset(input: T)(见 stdlib/libs/std/io/buffered_input_stream.cj)将实例绑定到新的输入流,并把curPos、availLen清零,但不会重新分配缓冲区(capacity 不变)。官方文档示例演示了先后绑定两个ByteBuffer、分别读取 "First message: Hello" 与 "Second message: World" 的用法,适用于流对象复用的场景。
4.2 Resource 能力:close 与自动释放
当被绑定的流实现了Resource接口时,BufferedInputStream通过扩展也实现Resource(见 stdlib/libs/std/io/buffered_input_stream.cj):
close():关闭当前流,实际转发给底层inputStream.close();关闭后不应再调用其他接口;isClosed(): Bool:查询底层流是否已关闭。
因此BufferedInputStream实例可直接用于try (r = bufferedStream) { ... }的 try-with-resource 语法,块结束时自动调用close()释放资源,官方 API 文档中给出了完整的自定义TestStream <: InputStream & Resource验证示例,运行输出:
Get the resource false Hello World Resource is closed true4.3 Seekable 能力:随机访问与长度查询
当被绑定的流实现了Seekable接口时,BufferedInputStream同步获得随机访问能力(见 stdlib/libs/std/io/buffered_input_stream.cj):
| 成员 | 功能 |
|---|---|
seek(sp: SeekPosition): Int64 | 移动底层流光标,并清空内部缓冲区(curPos = 0, availLen = 0),返回偏移量 |
prop position: Int64 | 返回当前光标位置 |
prop remainLength: Int64 | 返回从当前光标到流末尾的未读字节数(不含缓冲区中已缓存的数据) |
prop length: Int64 | 返回流的字节总长度 |
SeekPosition是std.io提供的定位枚举(见 stdlib/libs/std/io/io_utils.cj):
public enum SeekPosition { | Current(Int64) // 从当前位置移动指定偏移 | Begin(Int64) // 从流起点移动指定偏移 | End(Int64) // 从流末尾移动指定偏移 }官方文档示例还演示了边界行为:移动到数据头部之前(如Begin(-1))会抛出IOException(消息 "Failed to seek: position '-1' is before the beginning of the stream."),而超过数据末尾的位置合法。
五、配套工具函数:让缓冲流读取更省事
std.io包在 stdlib/libs/std/io/io_utils.cj 提供了一批基于InputStream/Seekable的工具函数,可与BufferedInputStream组合使用:
readToEnd<T>(from: T): Array<Byte>:一次取回流中所有未读数据;当流长度未知或超过 1GB 上限时,退化为以 4096 字节块循环读取的兜底路径(对应BLOCK_SIZE = 4096);readString<T>(from: T): String:按remainLength预分配缓冲并读取剩余内容,再经 UTF-8 校验转换为字符串,非法编码抛ContentFormatException;readStringUnchecked<T>(from: T): String:unsafe 版本,跳过 UTF-8 合法性检查以换取性能,需在unsafe {}块中调用;copy(from: InputStream, to!: OutputStream): Int64:以 4096 字节块把输入流剩余数据搬运到输出流,返回拷贝字节数。
官方文档为每个函数都提供了可运行示例,例如copy示例的输出为:
Copied 13 bytes. Data copied to target stream: Hello, World!六、总结与实践要点
回到本文开头的示例,一条完整的BufferedInputStream使用链路是:源数据 →ByteBuffer(底层流) →BufferedInputStream包装 →read/readByte消费 → 工具函数辅助收尾。实践中的关键要点归纳如下:
- 选对构造器:默认 4096 字节容量适合大多数场景;数据量小且追求低延迟可选更小容量;性能敏感场景可通过传入
Array<Byte>复用缓冲区减少分配; - 注意结束判定:
read返回 0 表示流结束;readByte返回None表示流结束; - 善用组合能力:底层流支持时,可无缝使用
close/isClosed(资源管理)与seek/position/remainLength/length(随机访问); - 搭配工具函数:
readToEnd、readString、copy能大幅简化"读光全部数据""转字符串""流拷贝"等常见操作。
所有代码示例均可在当前仓库中直接验证:示例文档见 stdlib/doc/libs/std/io/io_samples/buffered_input_stream.md,类实现见 stdlib/libs/std/io/buffered_input_stream.cj,完整 API 参考见 stdlib/doc/libs/std/io/io_package_api/io_package_classes.md。
- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
相关推荐
仓颉编程语言 BufferedInputStream 缓冲输入流详解:从示例到源码级原理
仓颉编程语言 BufferedInputStream 缓冲输入流详解:从示例到源码级原理 本文基于仓颉标准库 std.io 中 BufferedInputStr
文档知识库教程OpenHarmony仓颉标准库 std.io 流式 I/O 完全指南:接口设计、缓冲机制与字符读写实战
仓颉标准库 std.io 流式 I/O 完全指南:接口设计、缓冲机制与字符读写实战 std.io 是仓颉标准库中负责 程序与外部设备(文件、网络、内存缓冲区等)
编程语言语言运行时标准库垃圾回收并发编程Java字节缓冲流BufferedInputStream与BufferedOutputStream高效读写实战详解
Java字节缓冲流BufferedInputStream与BufferedOutputStream高效读写实战详解 导读 本篇文章聚焦 Java IO 体系中的
教程技术博客文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考