bit_buffer 是一个面向协议解析、底层二进制处理和位字段编解码的 Dart 库。
它适合这类场景:
- 给定一段
Uint8List,按几个字节或几个 bit 提取字段 - 按协议文档中的“字节偏移 + bit 偏移”解析数据
- 在
int、BigInt、float32、float64、UTF-8 和原始字节之间转换 - 构建或解析 CAN 报文、寄存器映射、自定义二进制协议
库同时提供:
ByteBufferStorage:基于Uint8List的字节后端,适合高效协议解析BitBufferStorage:位压缩后端,适合更细粒度的 bit 存储BinaryReader/BinaryWriter:顺序读写 APIImmutableBinaryView:零拷贝只读视图- 显式的
BitOrder和ByteOrder
- 支持
bool、有符号/无符号整数、BigInt、float32、float64、UTF-8 和原始字节 - 同时支持按绝对
bitOffset访问,以及按byteOffset + bitOffsetInByte访问 - 适合协议字段解析,也适合顺序式读写编码
- 字节后端支持高效的字节对齐读写和零拷贝字节视图
- 支持
view(...)创建局部视图,支持copy(...)创建独立副本
在 pubspec.yaml 中添加:
dependencies:
stun:
git:
url: https://github.com/halifox/dart_bit_buffer
ref: ^3.0.0导入:
import 'package:bit_buffer/bit_buffer.dart';下面的例子展示了一个很典型的协议解析入口:先包装已有字节数组,再依次读取单个 bit、连续多个 bit、多字节整数和 UTF-8 字符串。
import 'dart:typed_data';
import 'package:bit_buffer/bit_buffer.dart';
void main() {
// 示例报文:标志位 + 3 bit 模式字段 + 小端 16 位整数 + UTF-8 文本 "ABC"
final payload = Uint8List.fromList([0x91, 0xAC, 0x34, 0x12, 0x03, 0x41, 0x42, 0x43]);
final buffer = ByteBufferStorage.wrap(payload);
final flag = buffer.readBoolAt(0, bitOffsetInByte: 0); // 第 1 个字节最高位
final mode = buffer.readBitsAt(1, bitOffsetInByte: 2, bitLength: 3); // 第 2 个字节,从第 3 个 bit 起读 3 bit
final value = buffer.readUintAt(2, bitLength: 16, byteOrder: ByteOrder.littleEndian); // 第 3~4 个字节,小端 16 位整数
final text = buffer.readUtf8At(5, 3); // 第 6~8 个字节,按 UTF-8 解码
print(flag); // 标志位:true
print(mode); // 模式字段:5
print(value); // 小端 16 位值:4660
print(text); // 文本字段:ABC
}这个示例里的关键参数可以这样理解:
readBoolAt(0, bitOffsetInByte: 0):读取第1个字节的最高位readBitsAt(1, bitOffsetInByte: 2, bitLength: 3):从第2个字节的第3个 bit 开始,连续读取3个 bitreadUintAt(2, bitLength: 16, byteOrder: ByteOrder.littleEndian):从第3个字节开始读取16个 bit,并按小端解释readUtf8At(5, 3):从第6个字节开始读取3个字节,并按 UTF-8 解码bitOffsetInByte的取值范围是0..7,其中0表示字节最高位,7表示最低位
readXXX 系列方法可以分成两类:
readBool(...)、readBit(...)、readBits(...)、readUint(...)、readInt(...)、readBytes(...)、readUtf8(...)这类方法使用绝对bitOffset,适合你已经知道字段在整个缓冲区里的 bit 起点readBoolAt(...)、readBitAt(...)、readBitsAt(...)、readUintAt(...)、readIntAt(...)、readBytesAt(...)、readUtf8At(...)这类方法使用byteOffset,更适合照着协议表里的“第几个字节、第几个 bit”来解析
常用参数说明:
bitOffset:从整个缓冲区开头开始计算的绝对 bit 偏移,0表示第1个字节的最高位byteOffset:从第几个字节开始,0表示第1个字节bitOffsetInByte:当前字节内的 bit 偏移,范围是0..7,其中0表示最高位,7表示最低位bitLength:连续读取多少个 bitbyteLength:连续读取多少个字节bitOrder:字段内部 bit 的方向,默认是BitOrder.msbFirstbyteOrder:多字节整数或浮点数的字节序,默认是ByteOrder.bigEndian
最短示例:
final flag = buffer.readBool(bitOffset: 0); // 第 1 个字节最高位
final mode = buffer.readBits(bitOffset: 10, bitLength: 3); // 从绝对第 10 个 bit 起读 3 bit
final value = buffer.readUintAt(2, bitLength: 16, byteOrder: ByteOrder.littleEndian); // 从第 3 个字节起读 16 bit
final bytes = buffer.readBytesAt(4, 2); // 从第 5 个字节起读 2 个字节
final text = buffer.readUtf8At(6, 3); // 从第 7 个字节起读 3 个字节并解码如果你只想快速判断应该用哪个 readXXX:
- 读单个 bit:用
readBool(...)/readBit(...)或readBoolAt(...)/readBitAt(...) - 读多个 bit:用
readBits(...)/readBitsAt(...) - 读整数:用
readUint(...)/readUintAt(...)或readInt(...)/readIntAt(...) - 读字节:用
readBytes(...)/readBytesAt(...) - 读字符串:用
readUtf8(...)/readUtf8At(...)
如果你已经拿到一段原始字节数组,通常从 ByteBufferStorage.wrap(...) 开始:
import 'dart:typed_data';
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final payload = Uint8List.fromList([0x12, 0x34, 0x56]);
final buffer = ByteBufferStorage.wrap(payload);
final field = buffer.readUint(bitOffset: 4, bitLength: 12);
print(field); // 564 == 0x234
}如果协议文档写的是“第几个字节的第几个 bit”,优先使用 read*At(...):
import 'dart:typed_data';
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final payload = Uint8List.fromList([0x91, 0xAC, 0x34, 0x12]);
final buffer = ByteBufferStorage.wrap(payload);
print(buffer.readBoolAt(0, bitOffsetInByte: 0)); // true
print(buffer.readUintAt(1, bitOffsetInByte: 2, bitLength: 3)); // 5
print(buffer.readUintAt(2, bitLength: 16, byteOrder: ByteOrder.littleEndian)); // 4660
}当你在构建报文,或者按固定顺序连续解析字段时,BinaryWriter / BinaryReader 更方便:
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final MutableBinaryBuffer buffer = ByteBufferStorage();
final BinaryWriter writer = buffer.writer();
writer.writeBool(true);
writer.writeUint(0x1234, bitLength: 16, byteOrder: ByteOrder.littleEndian);
writer.writeBits(BigInt.from(5), bitLength: 3);
writer.writeUtf8('OK');
final BinaryReader reader = buffer.reader();
print(reader.readBool()); // true
print(reader.readUint(bitLength: 16, byteOrder: ByteOrder.littleEndian)); // 4660
print(reader.readBits(bitLength: 3)); // 5
print(reader.readUtf8(2)); // OK
}当你只想看一段局部 bit 区间,而不想复制底层数据时,可以使用 view(...):
import 'dart:typed_data';
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final source = Uint8List.fromList([0x12, 0x34, 0x56, 0x78]);
final buffer = ByteBufferStorage.wrap(source);
final view = buffer.view(bitOffset: 4, bitLength: 12);
print(view.readUint(bitOffset: 0, bitLength: 12)); // 564 == 0x234
final clone = view.copy(storageKind: BinaryStorageKind.bit);
print(clone.readUint(bitOffset: 0, bitLength: 12)); // 564
}如果你更关心位级存储,可以使用 BitBufferStorage:
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final buffer = BitBufferStorage();
buffer.writeBits(0, BigInt.from(43), bitLength: 6);
print(buffer.readBits(bitOffset: 0, bitLength: 6)); // 43
print(buffer.toUint8List()); // [172]
}- 你已经有一段
Uint8List - 你主要在做协议解析、报文读写、基础类型转换
- 你希望字节对齐字段有更好的性能
- 你希望在条件满足时通过
asUint8ListView()直接拿到底层字节视图
通常解析外部输入时,推荐这样开始:
final buffer = ByteBufferStorage.wrap(bytes);- 你更关心位级压缩存储
- 你需要大量 bit 级写入
- 你不依赖直接暴露底层
Uint8List
已知字段的绝对 bit 起始位置时,直接使用:
final value = buffer.readUint(bitOffset: 11, bitLength: 13);协议文档按“第几个字节、第几个 bit”描述字段时,更推荐使用:
final value = buffer.readUintAt(1, bitOffsetInByte: 2, bitLength: 3);这里有两个重要约定:
bitOffsetInByte采用0..7的 0-based 计数bitOffsetInByte == 0表示该字节的最高位,不是最低位
BitOrder控制字段内部 bit 的方向ByteOrder控制多字节数值的字节顺序
例如:
import 'package:bit_buffer/bit_buffer.dart';
void main() {
final buffer = ByteBufferStorage();
buffer.writeUint(0, 0x1234, bitLength: 16, bitOrder: BitOrder.lsbFirst, byteOrder: ByteOrder.littleEndian);
print(buffer.toUint8List()); // [44, 72]
}ByteBufferStorage.wrap(bytes)是零拷贝包装。修改bytes会影响缓冲区,修改缓冲区也会反映到bytesview(...)返回零拷贝只读视图;copy(...)返回拥有独立底层存储的可变副本readBytes(...)读取的是完整字节;如果最后一个字节只写了一部分 bit,请使用toUint8List()导出带零填充的结果asUint8ListView()只有在缓冲区是字节后端且当前长度字节对齐时才可能返回非空writeUint(...)和writeInt(...)会检查值是否能放进指定的位宽,不符合时会抛出RangeError- 如果字段宽度可能超过普通
int,请使用readUnsignedBigInt(...)/readBigInt(...)
dart analyze
dart test本项目使用 LGPL-3.0 License。