适用于 .NET Framework 的 BitStream 类






4.73/5 (27投票s)
一篇关于使用 BitStream 类读取和写入可变长度数据的文章。
引言
许多算法,例如数据压缩中使用的算法,都处理可变长度数据。可变长度数据是指无法完全表示为原始数据类型的那些数据。例如,.NET Framework 具有以下原始数据类型
原始数据类型 | C# 等效类型 | 标称存储分配(比特) |
System.Byte | byte | 8 |
System.SByte | sbyte | 8 |
System.Boolean | bool | 16 |
System.Char | char | 16 |
System.UInt16 | ushort | 16 |
System.Int16 | short | 16 |
System.UInt32 | uint | 32 |
System.Int32 | int | 32 |
System.UInt64 | ulong | 64 |
System.Int64 | long | 64 |
System.Single | float | 32 |
System.Double | double | 64 |
另一方面,可变长度数据没有为其定义固定的标称存储分配。每个数据元素可以包含任意数量的比特,例如 1 比特、12 比特、23 比特等。
.NET Framework 提供了两个类来帮助读取和写入可变长度数据:BitVector32
类和 BitArray
类。
BitVector32
类(命名空间:System.Collections.Specialized
)将布尔值和小整数存储在 32 比特的内存中,这对于 x86 32 位处理非常理想。但是,其主要缺点是它只能用于存储 32 比特数据,并且其结构对于它设计的简单任务来说往往过于抽象。
BitArray
类(命名空间:System.Collections
)管理一个紧凑的比特值数组,这些比特值表示为布尔值。其内部缓冲区是 32 位整数数组。与 BitVector32
类一样,这对于 x86 32 位处理非常理想。但是,其缓冲区长度固定,一旦实例化就无法按需扩展。BitArray
类的另一个问题是它主要具有一次读取和写入一个比特的方法,这对于大量数据来说效率低下。
本文重点介绍 BitStream
类,我认为它结合了 BitVector32
和 BitArray
类的最佳元素,并解决了它们的弱点。BitStreamSample 解决方案和 BitStream
类是用 C# 编写的,我使用 Visual Studio.NET 2003 成功地使用 .NET Framework v1.1 编译了它们。
可扩展流
BitStream
类的内部缓冲区是 32 位无符号整数数组
private uint [] _auiBitBuffer;
此缓冲区会根据调用应用程序的需求自动扩展。
内部缓冲区以 **大端** 字节格式存储 **多字节** 数据。例如,System.Int32
值 65500 在内部缓冲区中存储为
字节偏移 | 字节中的比特 |
00 | 00000000 |
01 | 00000000 |
02 | 11111111 |
03 | 11011100 |
BitStream
类有三个构造函数
public BitStream();
public BitStream(long capacity);
public BitStream(Stream bits);
第二个构造函数中的 capacity
参数表示内部缓冲区的大致最终长度(以 **比特** 为单位)。更精确的估算会导致更快的 **写入** 操作。这是因为在运行时扩展 BitStream
的内部缓冲区需要更少的内存重新分配。
写入 BitStream
BitStream
类中有几个重载的 Write
方法。它们包括从写入单个比特到 BitStream
public virtual void Write(bool bit);
到写入 64 位元素的数组到 BitStream
public virtual void Write(ulong [] bits, int offset, int count);
public virtual void Write(long [] bits, int offset, int count);
public virtual void Write(decimal [] bits, int offset, int count);
还有一些方法允许您从原始数据类型中写入指定数量的比特到 BitStream
public virtual void Write(byte bits, int bitIndex, int count);
public virtual void Write(sbyte bits, int bitIndex, int count);
public virtual void Write(char bits, int bitIndex, int count);
public virtual void Write(ushort bits, int bitIndex, int count);
public virtual void Write(short bits, int bitIndex, int count);
public virtual void Write(uint bits, int bitIndex, int count);
public virtual void Write(int bits, int bitIndex, int count);
public virtual void Write(ulong bits, int bitIndex, int count);
public virtual void Write(long bits, int bitIndex, int count);
public virtual void Write(float bits, int bitIndex, int count);
public virtual void Write(double bits, int bitIndex, int count);
在上述方法中,bits
参数指定要从中写入数据的 **比特**。
bitIndex
参数指定要开始写入的 **小端** **比特** 索引。
count
参数指定要写入的最大 **比特** 数。
例如,字节值 217 的二进制表示形式为
比特索引 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 1 |
在这里,**比特零** 是最低有效位 (LSB),比特 7 是最高有效位 (MSB)。
将字节值 217 的比特 1 到 6 写入 BitStream
的语句是
Write((byte)217, 1, 6);
有关写入 BitStream 的更多信息,请参阅 BitStream 类参考。
从 BitStream 读取
与写入 BitStream
一样,BitStream
类中有几个重载的 Read
方法。它们包括从读取单个比特
public virtual int Read(out bool bit);
到从 BitStream
读取 64 位元素的数组
public virtual int Read(ulong [] bits, int offset, int count);
public virtual int Read(long [] bits, int offset, int count);
public virtual int Read(double [] bits, int offset, int count);
还有一些方法允许从 BitStream
读取指定数量的比特到原始数据类型
public virtual int Read(out byte bits, int bitIndex, int count);
public virtual int Read(out sbyte bits, int bitIndex, int count);
public virtual int Read(out char bits, int bitIndex, int count);
public virtual int Read(out ushort bits, int bitIndex, int count);
public virtual int Read(out short bits, int bitIndex, int count);
public virtual int Read(out uint bits, int bitIndex, int count);
public virtual int Read(out int bits, int bitIndex, int count);
public virtual int Read(out ulong bits, int bitIndex, int count);
public virtual int Read(out long bits, int bitIndex, int count);
public virtual int Read(out float bits, int bitIndex, int count);
public virtual int Read(out double bits, int bitIndex, int count);
在上述方法中,bits
参数包含 bitIndex
和 (bitIndex + count - 1
) 之间的 **比特**,这些比特被从当前 BitStream
读取的 **比特** 所替换。
bitIndex
参数指定要开始读取的 **比特** 索引。
count
参数指定要读取的最大 **比特** 数。
返回值指定写入原始数据类型的 **比特** 数量。如果当前可用的比特数量少于请求的比特数量,则此值可能小于请求的比特数量;如果到达当前 BitStream
的末尾但未读取任何比特,则为零。
设计这组 Read
方法使用 out
参数的主要原因是为了保持一致性。BitStream
类中的所有 Read
方法都设计为在读取超出流末尾时不会抛出异常。相反,它们会返回实际读取的比特数。因此,BitStream
类中的所有 Read
方法都返回一个定义实际读取比特数的单个原始数据类型。
例如,从 BitStream
读取 7 比特并将其存储在 byte
值中(从比特索引 1 开始)的语句是
byte bytValue;
int iBitsRead = Read(out bytValue, 1, 7);
有关从 BitStream 读取的更多信息,请参阅 BitStream 类参考。
其他方法
BitStream
类包含执行逻辑操作的方法
public virtual BitStream And(BitStream bits);
public virtual BitStream Or(BitStream bits);
public virtual BitStream Xor(BitStream bits);
public virtual BitStream Not();
以及对整个流执行左移和右移位操作的方法
public virtual BitStream ShiftLeft(long count);
public virtual BitStream ShiftRight(long count);
还有几个重载的 ToString
方法,用于以二进制表示法显示 BitStream
和原始数据类型的内容
public override string ToString();
public static string ToString(bool bit);
public static string ToString(byte bits);
public static string ToString(char bits);
public static string ToString(sbyte bits);
public static string ToString(ushort bits);
public static string ToString(short bits);
public static string ToString(uint bits);
public static string ToString(int bits);
public static string ToString(ulong bits);
public static string ToString(long bits);
public static string ToString(float bits);
public static string ToString(double bits);
这些方法在调试期间可能非常有用。
这些和其他方法的详细文档可以在 BitStream 类参考中找到。
BitStreamSample 应用程序
BitStreamSample 应用程序让您能够试用 BitStream
类。
它在左侧包含一个 BitStream 属性面板,显示容量、长度、位置(读/写)、最后写入 BitStream
的比特数以及最后从 BitStream
读取的比特数。它还以二进制表示法显示 BitStream
内部缓冲区的内容。这些字段会在数据读写 BitStream
时自动更新。
使用 **写入** 选项卡面板写入 BitStream
。它包含一个原始数据类型列表。每种数据类型都有一个可修改的值、比特索引和计数,以及一个与写入关联的写入按钮()。
使用 **读取** 选项卡面板从 BitStream
读取。它还包含与 **写入** 选项卡中相同的原始数据类型列表。每种数据类型都有一个可修改的比特索引和计数,以及一个与读取关联的读取按钮()。值字段仅用于显示目的。
注意:“读取”和“写入”选项卡面板中的“比特”类型实际上是 System.Boolean
,为了更清晰地显示和方便输入,它被转换为 System.Int32
类型。“字符”类型在写入 BitStream
时接受单个字符,在从 BitStream
读取时显示单个字符。
历史
- 2005年11月16日
- 初始发布。
- 2005年11月23日
- 在文章中添加了注释,以澄清
BitStream
以大端字节格式存储多字节数据。 - 添加了类构造函数,该构造函数使用指定流提供的比特来初始化
BitStream
类的新实例。
- 在文章中添加了注释,以澄清
- 2005年11月24日
- 添加了
public virtual byte [] ToByteArray();
方法。 BitStream
类现在支持public override int ReadByte();
和public override void WriteByte(byte value);
方法。
- 添加了
- 2005年11月25日
- 添加了以下隐式运算符,允许类型将
BitStream
类的实例与其它类型的流对象进行相互转换public static implicit operator BitStream(MemoryStream bits); public static implicit operator MemoryStream(BitStream bits); public static implicit operator BitStream(FileStream bits); public static implicit operator BitStream(BufferedStream bits); public static implicit operator BufferedStream(BitStream bits); public static implicit operator BitStream(NetworkStream bits); public static implicit operator BitStream(CryptoStream bits);
- 添加了以下隐式运算符,允许类型将
- 2005年11月27日
- 添加了
public virtual void WriteTo(Stream bits);
以将当前比特流的内容写入另一个流。
- 添加了
- 2005年12月01日
- 修复了
public virtual void Write(ulong bits, int bitIndex, int count)
和public virtual int Read(out ulong bits, int bitIndex, int count)
方法的问题。
- 修复了