65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 .NET Framework 的 BitStream 类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (27投票s)

2005年11月16日

CPOL

7分钟阅读

viewsIcon

110196

downloadIcon

4597

一篇关于使用 BitStream 类读取和写入可变长度数据的文章。

引言

许多算法,例如数据压缩中使用的算法,都处理可变长度数据。可变长度数据是指无法完全表示为原始数据类型的那些数据。例如,.NET Framework 具有以下原始数据类型

原始数据类型C# 等效类型标称存储分配(比特)
System.Bytebyte8
System.SBytesbyte8
System.Booleanbool16
System.Charchar16
System.UInt16ushort16
System.Int16short16
System.UInt32uint32
System.Int32int32
System.UInt64ulong64
System.Int64long64
System.Singlefloat32
System.Doubledouble64

另一方面,可变长度数据没有为其定义固定的标称存储分配。每个数据元素可以包含任意数量的比特,例如 1 比特、12 比特、23 比特等。

.NET Framework 提供了两个类来帮助读取和写入可变长度数据:BitVector32 类和 BitArray 类。

BitVector32 类(命名空间:System.Collections.Specialized)将布尔值和小整数存储在 32 比特的内存中,这对于 x86 32 位处理非常理想。但是,其主要缺点是它只能用于存储 32 比特数据,并且其结构对于它设计的简单任务来说往往过于抽象。

BitArray 类(命名空间:System.Collections)管理一个紧凑的比特值数组,这些比特值表示为布尔值。其内部缓冲区是 32 位整数数组。与 BitVector32 类一样,这对于 x86 32 位处理非常理想。但是,其缓冲区长度固定,一旦实例化就无法按需扩展。BitArray 类的另一个问题是它主要具有一次读取和写入一个比特的方法,这对于大量数据来说效率低下。

本文重点介绍 BitStream 类,我认为它结合了 BitVector32BitArray 类的最佳元素,并解决了它们的弱点。BitStreamSample 解决方案和 BitStream 类是用 C# 编写的,我使用 Visual Studio.NET 2003 成功地使用 .NET Framework v1.1 编译了它们。

可扩展流

BitStream 类的内部缓冲区是 32 位无符号整数数组

private uint [] _auiBitBuffer;

此缓冲区会根据调用应用程序的需求自动扩展。

内部缓冲区以 **大端** 字节格式存储 **多字节** 数据。例如,System.Int32 值 65500 在内部缓冲区中存储为

字节偏移字节中的比特
0000000000
0100000000
0211111111
0311011100

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 的二进制表示形式为

比特索引76543210
11011001

在这里,**比特零** 是最低有效位 (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) 方法的问题。
© . All rights reserved.