在C#中使用任意位位置和长度提取字节边界的值

我目前正在研究一种网络工具,它需要对特定协议进行解码/编码,该协议将字段打包到任意位置的密集位数组中。 例如,协议的一部分使用3个字节来表示许多不同的字段:

Bit Position(s) Length (In Bits) Type 0 1 bool 1-5 5 int 6-13 8 int 14-22 9 uint 23 1 bool 

如您所见,有几个字段跨越多个字节。 许多(大多数)也比可能用于表示它们的内置类型短,例如第一个只有5位长的int字段。 在这些情况下,目标类型的最高有效位(例如Int32或Int16)应填充为0以弥补差异。

我的问题是我很难处理这类数据。 具体来说,我很难弄清楚如何有效地获取任意长度的位数组,用源缓冲区中的相应位填充它们,填充它们以匹配目标类型,并将填充的位数组转换为目标类型。 在理想的世界中,我将能够获取上面示例中的byte [3]并调用GetInt32(byte[] bytes, int startBit, int length)

我发现的最接近的东西是BitStream类,但它似乎希望单个值在字节/字边界上排列(并且类的半流/半索引访问约定使它变得有点混乱)。

我自己的第一次尝试是使用BitArray类,但事实certificate这有点笨拙。 很容易将缓冲区中的所有位填充到一个大的BitArray ,只将你想要的那些从源BitArray转移到一个新的临时BitArray ,然后将其转换为目标值…但它似乎是错误的,而且非常耗时的。

我现在正在考虑一个类如下的类,它引用(或创建)源/目标byte []缓冲区以及偏移量,并为某些目标类型提供get和set方法。 棘手的部分是获取/设置值可能跨越多个字节。

 class BitField { private readonly byte[] _bytes; private readonly int _offset; public BitField(byte[] bytes) : this(bytes, 0) { } public BitField(byte[] bytes, int offset) { _bytes = bytes; _offset = offset; } public BitField(int size) : this(new byte[size], 0) { } public bool this[int bit] { get { return IsSet(bit); } set { if (value) Set(bit); else Clear(bit); } } public bool IsSet(int bit) { return (_bytes[_offset + (bit / 8)] & (1 << (bit % 8))) != 0; } public void Set(int bit) { _bytes[_offset + (bit / 8)] |= unchecked((byte)(1 << (bit % 8))); } public void Clear(int bit) { _bytes[_offset + (bit / 8)] &= unchecked((byte)~(1 << (bit % 8))); } //startIndex = the index of the bit at which to start fetching the value //length = the number of bits to include - may be less than 32 in which case //the most significant bits of the target type should be padded with 0 public int GetInt32(int startIndex, int length) { //NEED CODE HERE } //startIndex = the index of the bit at which to start storing the value //length = the number of bits to use, if less than the number of bits required //for the source type, precision may be lost //value = the value to store public void SetValue(int startIndex, int length, int value) { //NEED CODE HERE } //Other Get.../Set... methods go here } 

我正在寻找这方面的任何指导,例如第三方库,在跨越多个字节的任意位位置获取/设置值的算法,对我的方法的反馈等等。我在上面的课程中进行了澄清,并不一定要查找代码填写(虽然我不会争论是否有人想要解决它!)。

如果您的数据包总是小于8或4个字节,那么将每个数据包存储在Int32Int64会更容易。 字节数组只会使事情复杂化。 你必须注意High-Endian和Low-Endian存储。

然后,对于一个3字节的包:

 public static void SetValue(Int32 message, int startIndex, int length, int value) { // we want lengthx1 int mask = (1 << length) - 1; value = value & mask; // or check and throw int offset = 24 - startIndex - length; // 24 = 3 * 8 message = message | (value << offset); } 

正如所承诺的,这是我为此目的而创建的课程。 它将在任意指定的索引处包装任意字节数组,并允许在位级别进行读/写。 它提供了从其他字节数组读取/写入任意位块或使用用户定义的偏移量和长度读取/写入原始值的方法。 它对我的情况非常有效,并解决了我上面提到的确切问题。 但是,它确实有一些缺点。 首先是它显然没有大量记录 -​​ 我只是没有时间。 第二是没有界限或其他检查。 它目前还需要MiscUtil库来提供字节序转换。 所有这一切,希望这可以帮助解决或作为具有类似用例的其他人的起点。

 internal class BitField { private readonly byte[] _bytes; private readonly int _offset; private EndianBitConverter _bitConverter = EndianBitConverter.Big; public BitField(byte[] bytes) : this(bytes, 0) { } //offset = the offset (in bytes) into the wrapped byte array public BitField(byte[] bytes, int offset) { _bytes = bytes; _offset = offset; } public BitField(int size) : this(new byte[size], 0) { } //fill == true = initially set all bits to 1 public BitField(int size, bool fill) : this(new byte[size], 0) { if (!fill) return; for(int i = 0 ; i < size ; i++) { _bytes[i] = 0xff; } } public byte[] Bytes { get { return _bytes; } } public int Offset { get { return _offset; } } public EndianBitConverter BitConverter { get { return _bitConverter; } set { _bitConverter = value; } } public bool this[int bit] { get { return IsBitSet(bit); } set { if (value) SetBit(bit); else ClearBit(bit); } } public bool IsBitSet(int bit) { return (_bytes[_offset + (bit / 8)] & (1 << (7 - (bit % 8)))) != 0; } public void SetBit(int bit) { _bytes[_offset + (bit / 8)] |= unchecked((byte)(1 << (7 - (bit % 8)))); } public void ClearBit(int bit) { _bytes[_offset + (bit / 8)] &= unchecked((byte)~(1 << (7 - (bit % 8)))); } //index = the index of the source BitField at which to start getting bits //length = the number of bits to get //size = the total number of bytes required (0 for arbitrary length return array) //fill == true = set all padding bits to 1 public byte[] GetBytes(int index, int length, int size, bool fill) { if(size == 0) size = (length + 7) / 8; BitField bitField = new BitField(size, fill); for(int s = index, d = (size * 8) - length ; s < index + length && d < (size * 8) ; s++, d++) { bitField[d] = IsBitSet(s); } return bitField._bytes; } public byte[] GetBytes(int index, int length, int size) { return GetBytes(index, length, size, false); } public byte[] GetBytes(int index, int length) { return GetBytes(index, length, 0, false); } //bytesIndex = the index (in bits) into the bytes array at which to start copying //index = the index (in bits) in this BitField at which to put the value //length = the number of bits to copy from the bytes array public void SetBytes(byte[] bytes, int bytesIndex, int index, int length) { BitField bitField = new BitField(bytes); for (int i = 0; i < length; i++) { this[index + i] = bitField[bytesIndex + i]; } } public void SetBytes(byte[] bytes, int index, int length) { SetBytes(bytes, 0, index, length); } public void SetBytes(byte[] bytes, int index) { SetBytes(bytes, 0, index, bytes.Length * 8); } //UInt16 //index = the index (in bits) at which to start getting the value //length = the number of bits to use for the value, if less than required the value is padded with 0 public ushort GetUInt16(int index, int length) { return _bitConverter.ToUInt16(GetBytes(index, length, 2), 0); } public ushort GetUInt16(int index) { return GetUInt16(index, 16); } //valueIndex = the index (in bits) of the value at which to start copying //index = the index (in bits) in this BitField at which to put the value //length = the number of bits to copy from the value public void Set(ushort value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(ushort value, int index) { Set(value, 0, index, 16); } //UInt32 public uint GetUInt32(int index, int length) { return _bitConverter.ToUInt32(GetBytes(index, length, 4), 0); } public uint GetUInt32(int index) { return GetUInt32(index, 32); } public void Set(uint value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(uint value, int index) { Set(value, 0, index, 32); } //UInt64 public ulong GetUInt64(int index, int length) { return _bitConverter.ToUInt64(GetBytes(index, length, 8), 0); } public ulong GetUInt64(int index) { return GetUInt64(index, 64); } public void Set(ulong value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(ulong value, int index) { Set(value, 0, index, 64); } //Int16 public short GetInt16(int index, int length) { return _bitConverter.ToInt16(GetBytes(index, length, 2, IsBitSet(index)), 0); } public short GetInt16(int index) { return GetInt16(index, 16); } public void Set(short value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(short value, int index) { Set(value, 0, index, 16); } //Int32 public int GetInt32(int index, int length) { return _bitConverter.ToInt32(GetBytes(index, length, 4, IsBitSet(index)), 0); } public int GetInt32(int index) { return GetInt32(index, 32); } public void Set(int value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(int value, int index) { Set(value, 0, index, 32); } //Int64 public long GetInt64(int index, int length) { return _bitConverter.ToInt64(GetBytes(index, length, 8, IsBitSet(index)), 0); } public long GetInt64(int index) { return GetInt64(index, 64); } public void Set(long value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(long value, int index) { Set(value, 0, index, 64); } //Char public char GetChar(int index, int length) { return _bitConverter.ToChar(GetBytes(index, length, 2), 0); } public char GetChar(int index) { return GetChar(index, 16); } public void Set(char value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(char value, int index) { Set(value, 0, index, 16); } //Bool public bool GetBool(int index, int length) { return _bitConverter.ToBoolean(GetBytes(index, length, 1), 0); } public bool GetBool(int index) { return GetBool(index, 8); } public void Set(bool value, int valueIndex, int index, int length) { SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length); } public void Set(bool value, int index) { Set(value, 0, index, 8); } //Single and double precision floating point values must always use the correct number of bits public float GetSingle(int index) { return _bitConverter.ToSingle(GetBytes(index, 32, 4), 0); } public void SetSingle(float value, int index) { SetBytes(_bitConverter.GetBytes(value), 0, index, 32); } public double GetDouble(int index) { return _bitConverter.ToDouble(GetBytes(index, 64, 8), 0); } public void SetDouble(double value, int index) { SetBytes(_bitConverter.GetBytes(value), 0, index, 64); } } 

首先,您似乎已经使用System.Collections.BitArray类重新发明了这个轮子。 至于实际找到特定字段的值,我认为可以通过以下伪代码的一点数学魔术轻松完成:

  1. 从您选择中最远的数字开始(startIndex + length)。
  2. 如果已设置,则添加2 ^(距离数字的距离)。 在这种情况下,它将为0(mostDistance – self = 0)。 所以加2 ^ 0(1)。
  3. 向左移动一位。
  4. 对所需长度的每个数字重复一次。

在那种情况下,如果你有一个像这样的arrays:

10001010

你想要数字0-3的值,你会得到类似的东西:

 [Index 3] [Index 2] [Index 1] [Index 0] (3 - 3) (3 - 2) (3 - 1) (3 - 0) ============================================= (0 * 2^0) + (0 * 2^1) + (0 * 2^2) + (1 * 2^3) = 8 

由于1000(二进制)== 8,数学计算出来了。

使用简单的位移来获取您的价值有什么问题?

 int data = Convert.ToInt32( "110000000010000000100001", 2 ); bool v1 = ( data & 1 ) == 1; // True int v2 = ( data >> 1 ) & 0x1F; // 16 int v3 = ( data >> 6 ) & 0xFF; // 128 uint v4 = (uint )( data >> 14 ) & 0x1FF; // 256 bool v5 = ( data >> 23 ) == 1; // True 

这是一篇很好的文章,涵盖了这一主题。 它在C中,但相同的概念仍然适用。