如何从/向流写入/读取位? (C#)
Posted
技术标签:
【中文标题】如何从/向流写入/读取位? (C#)【英文标题】:How to write/read bits from/to a Stream? (C#) 【发布时间】:2009-08-22 12:02:56 【问题描述】:如何将位写入流 (System.IO.Stream) 或在 C# 中读取?谢谢。
【问题讨论】:
你能提供更多的上下文吗?你的意思是读/写二进制数据而不是文本数据?您打算读/写什么类型的数据? 你真的要写单个位(不是字节)吗? 问题很清楚,他需要一次向/从流中写入或读取一个位。这对于编写紧凑的流或 Huffman 压缩是典型的。 【参考方案1】:您可以在 Stream 上创建一个枚举位的扩展方法,如下所示:
public static class StreamExtensions
public static IEnumerable<bool> ReadBits(this Stream input)
if (input == null) throw new ArgumentNullException("input");
if (!input.CanRead) throw new ArgumentException("Cannot read from input", "input");
return ReadBitsCore(input);
private static IEnumerable<bool> ReadBitsCore(Stream input)
int readByte;
while((readByte = input.ReadByte()) >= 0)
for(int i = 7; i >= 0; i--)
yield return ((readByte >> i) & 1) == 1;
使用这种扩展方法很简单:
foreach(bool bit in stream.ReadBits())
// do something with the bit
注意:不要在同一个Stream上多次调用ReadBits,否则后续调用会忘记当前位的位置,直接开始读取下一个字节。
【讨论】:
整洁地使用扩展方法。但是,您可以添加对以相反顺序读取位的支持。 多次调用ReadBits
每次都会从流中读取一个新字节,即使前一个字节中还有未读取的位。我已经在下面发布了一个解决这个问题的答案。
是的,应该有一个字节序标志。
戴夫,这不正确。第一次迭代读取第一个字节并产生第一位。此时,执行在内部 for 循环内暂停。下一次迭代将从该点恢复并从第一个字节产生下一位。只有在内部 for 循环完成后(即 8 次迭代后)才会读取下一个字节。
@TommyCarlier 请参阅dotnetfiddle.net/Syk1kL - 您的解决方案会跳到下一个字节并随后调用ReadBits
。【参考方案2】:
这对于默认流类是不可能的。 C# (BCL) Stream 类在其最低级别的字节粒度上运行。您可以做的是编写一个包装类,该类读取字节并将它们划分为位。
例如:
class BitStream : IDisposable
private Stream m__stream;
private byte? m_current;
private int m_index;
public byte ReadNextBit()
if ( !m_current.HasValue )
m_current = ReadNextByte();
m_index = 0;
var value = (m_byte.Value >> m_index) & 0x1;
m_index++;
if (m_index == 8)
m_current = null;
return value;
private byte ReadNextByte()
...
// Dispose implementation omitted
注意:这将以从右到左的方式读取位,这可能是也可能不是您想要的。
【讨论】:
@Alon,我刚刚添加了一个关于如何做到这一点的快速示例 能否添加一个写位的例子,好吗? 流是 IDisposable:任何包装器(如 BitStream)本身应该是 IDisposable,或者(不太理想)明确记录它不处理 Disposal 的事实。 当你用完当前字节中的位时,你需要一种方法来表示需要读取下一个字节......当你的说明性示例开始成为现实时,你不讨厌它吗您永远不会亲自使用的代码。 :-) @Alon -- 这里的人们倾向于对“你会为我编写代码”类型的问题和 cmets 皱眉。鉴于此代码示例,您确实应该能够弄清楚如何编写位。您只需要将它们累积成一个字节,直到字节满,然后写入字节。【参考方案3】:如果您需要一次检索几位字节流的单独部分,则需要记住该位的位置,以便在调用之间读取下一个。下面的类负责在调用之间缓存当前字节和其中的位位置。
// Binary MSB-first bit enumeration.
public class BitStream
private Stream wrapped;
private int bitPos = -1;
private int buffer;
public BitStream(Stream stream) => this.wrapped = stream;
public IEnumerable<bool> ReadBits()
do
while (bitPos >= 0)
yield return (buffer & (1 << bitPos--)) > 0;
buffer = wrapped.ReadByte();
bitPos = 7;
while (buffer > -1);
这样调用:
var bStream = new BitStream(<existing Stream>);
var firstBits = bStream.ReadBits().Take(2);
var nextBits = bStream.ReadBits().Take(3);
...
【讨论】:
这个解决方案有一个问题:它只保留了缓冲区和 bitPos 变量的 1 个静态实例。当您同时在多个 Streams 上调用 ReadBits 时(例如,从多个线程),缓冲区和 bitPos 将无法为这些不同的 Streams 正确工作。此外,如果您在 1 个 Stream 上调用 ReadBits,完成并在新 Stream 上调用 ReadBits,缓冲区和 bitPos 仍将保留前一个 Stream 中的陈旧数据。 是的,扩展方法代码有限制,因为所有字段都必须是静态的,不允许您对同时打开的不同Stream
s 进行交叉读取。我已经把代码变成了一个常规的包装类。感谢您的反馈!以上是关于如何从/向流写入/读取位? (C#)的主要内容,如果未能解决你的问题,请参考以下文章