首页 > 解决方案 > 手动打包字节以在网络上发送

问题描述

我有一个具有以下变量的对象:

bool firstBool;  
float firstFloat; (0.0 to 1.0)  
float secondFloat (0.0 to 1.0)  
int firstInt; (0 to 10,000)

我正在使用 ToString 方法来获取可以通过网络发送的字符串。扩大规模我遇到了这个占用的数据量的问题。字符串现在看起来像这样: "false:1.0:1.0:10000"这是 19 个字符,每 2 个字节,所以 38 个字节

我知道我可以通过手动将数据存储在 4 个字节中来节省这个大小,如下所示:

A|B|B|B|B|B|B|B  
C|C|C|C|C|C|C|D  
D|D|D|D|D|D|D|D  
D|D|D|D|D|X|X|X  

A = bool(0 or 1), B = int(0 to 128), C = int(0 to 128), D = int(0 to 16384), X = Leftover bits  

我一直在尝试使用 BitArray 和 byte[] 将数据转换为二进制结构。

经过一些实验,我最终完成了这个序列化过程(我知道它需要清理和优化)

public byte[] Serialize() {
    byte[] firstFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(firstFloat * 128)); //Convert the float to int from (0 to 128)

    byte[] secondFloatBytes = BitConverter.GetBytes(Mathf.FloorToInt(secondFloat * 128)); //Convert the float to int from (0 to 128)

    byte[] firstIntData = BitConverter.GetBytes(Mathf.FloorToInt(firstInt)); // Get the bytes for the int

    BitArray data = new BitArray(32); // create the size 32 bitarray to hold all the data

    int i = 0; // create the index value

    data[i] = firstBool; // set the 0 bit

    BitArray ffBits = new BitArray(firstFloatBytes);
    for(i = 1; i < 8; i++) {
        data[i] = ffBits[i-1]; // Set bits 1 to 7
    }

    BitArray sfBits = new BitArray(secondFloatBytes);
    for(i = 8; i < 15; i++) {
        data[i] = sfBits[i-8]; // Set bits 8 to 14
    }

    BitArray fiBits = new BitArray(firstIntData);
    for(i = 15; i < 29; i++) {
        data[i] = fiBits[i-15]; // Set bits 15 to 28
    }
    byte[] output = new byte[4]; // create a byte[] to hold the output
    data.CopyTo(output,0); // Copy the bits to the byte[]
    return output;
}

从这种结构中取出信息比把它放入这种形式要复杂得多。我想我可能可以使用按位运算符和位掩码来锻炼一些东西。

事实证明这比我预期的要复杂。我认为访问 byte[] 的位以直接操作数据、提取位范围、然后转换回重建对象所需的值会非常容易。这种类型的数据序列化是否有最佳实践?有谁知道我可以阅读的教程或示例参考?

标签: c#serializationbit-manipulation

解决方案


标准且高效的序列化方法有:

  1. 使用BinaryWriter/ BinaryReader

    public byte[] Serialize()
    {
       using(var s = new MemoryStream())
       using(var w = new BinaryWriter(s))
       {
          w.Write(firstBool);
          w.Write(firstFloat);
          ...
          return s.ToArray();
       }
    }
    
    public void Deserialize(byte[] bytes)
    {
       using(var s = new MemoryStream(bytes))
       using(var r = new BinaryReader(s))
       {
          firstBool = r.ReadBool();
          firstFload = r.ReadFloat();
          ...
       }
    }
    
  2. 使用protobuf.net

BinaryWriter/BinaryReader快得多(大约 7 倍)。Protobuf 更灵活、更易于使用、非常流行,并且序列化的字节数减少了大约 33%。(当然,这些数字是数量级的,取决于您序列化的内容和方式)。

现在基本上BinaryWriter会写 1 + 4 + 4 + 4 = 13 个字节。您可以通过将值转换为 bool、byte、byte、short 来将其缩小到 5 个字节,然后按照您想要的方式对其进行舍入。最后,如果您真的想要,很容易将 bool 与您的字节之一合并以获得 4 个字节。

我并不反对手动序列化。但就性能而言,它必须物有所值。代码非常不可读。直接在字节上使用位掩码和二进制移位,但要尽可能简单。不要使用位数组。它很慢而且不可读。


推荐阅读