【问题标题】:Encode integer as variable-length big-endian byte-array将整数编码为变长大端字节数组
【发布时间】:2010-07-04 20:30:31
【问题描述】:

我需要将一个整数写入字节数组,以便省略前导零并且字节以大端顺序写入。

例子:

int    original = 0x00123456;

byte[] encoded  = Encode(original);  //  == new byte[] { 0x12, 0x34, 0x56 };

int    decoded  = Decode(encoded);   //  == 0x123456

我的Decode 方法:

private static int Decode(byte[] buffer, int index, int length)
{
    int result = 0;
    while (length > 0)
    {
        result = (result << 8) | buffer[index];
        index++;
        length--;
    }
    return result;
}

我正在努力想出一个 Encode 方法,该方法不需要临时缓冲区或在以小端顺序写入字节后反转字节。有人可以帮忙吗?

private static int Encode(int value, byte[] buffer, int index)
{

}

【问题讨论】:

  • 为什么 Encode() 需要一个 byte[] 参数?它应该返回一个。
  • @Hans Passant:在指定索引处写入需要一个字节[]。我想避免不必要的 byte[] 分配。
  • 好的,有道理。但是最终读取这个 byte[] 的代码应该如何知道它应该读取多少有效字节呢?
  • @Hans Passant:Encode 返回写入的字节数。该值沿编码整数存储,因此我可以在解码时将长度传递给Decode

标签: c# endianness


【解决方案1】:

根据 OP 的要求,这里有一个 32 位数字没有循环的版本:

private static int Encode(int value, byte[] buffer, int index)
{
    byte temp;
    bool leading = true;

    temp = (value >> 24) & 0xFF;
    if (temp > 0) {
      buffer[index++] = temp;
      leading = false;
    }

    temp = (value >> 16) & 0xFF;
    if (temp > 0 || leading == false) {
      buffer[index++] = temp;
      leading = false;
    }

    temp = (value >> 8) & 0xFF;
    if (temp > 0 || leading == false) {
      buffer[index++] = temp;
      leading = false;
    }

    temp = value & 0xFF;
    buffer[index++] = temp;

    return index;
}

对 32 位数字使用循环的版本:

private static int Encode(int value, byte[] buffer, int index)
{
    int length = 0;

    for (int i = 3; i >= 0; i++) {
      byte temp = (byte)(value >> (8 * i));
      if (temp > 0 || length > 0) {
        buffer[index++] = temp;
        length++;
      }
    }

    return length;
}

请注意,如果输入仅为 0,则此版本不会写入任何内容。

【讨论】:

  • -1。这是错误的。它不排除前导零。相反,它排除了值为零的每个字节。用 0x00120056 试试,你会发现结果不正确。
  • 对不起,我忽略了那部分。现在已经修好了。
  • @casablanca:我喜欢这个。请帮我一个小忙,把它变成一个循环(班次只有 8*i)。你也可以改变它,让它返回写入的字节数吗?
  • @dtb,把它放在一个循环中意味着你在循环中有一个条件分支。一旦你有了第一个非零字节,你就可以在没有条件分支的情况下盲目地打包剩下的字节。
  • @Chris Taylor:我正在寻找一种简单、可读、可维护的解决方案,不一定比其他解决方案花费的时间少几纳秒。如果你看到两个循环,你需要弄清楚每个循环在做什么,而如果只有一个循环,你立即知道这个循环只是以某种方式将一些字节写入 byte[]。
【解决方案2】:
private static int Encode(int value, byte[] buffer, int index)
{
    int length = 0;
    int valueCopy = value;
    while (valueCopy != 0)
    {
        valueCopy >>= 8;
        length++;
    }
    for (int i = 0; i < length; i++)
    {
        buffer[index + length - i - 1] = (byte)value;
        value >>= 8;
    }
    return length;
}

【讨论】:

  • 双循环有点难看,但远没有我迄今为止想出的那么难看。 +1。
  • @dtb:如果您的输入大小是固定的,即 32 位或 64 位,您可以完全取消循环并硬编码转换 - 它甚至会更快一点。
  • @casablanca:嗯,我的输入大小始终为 32 位(非负的int),但我想在将值写入字节 [] 时省略前导零。您能否发布一个答案来说明没有循环的解决方案的外观? (不省略前导零很简单;我正在努力处理可变长度输出。)
  • 循环需要找到目标数字的长度。
  • @digEmAll:如果您知道迭代次数,您只能展开循环,这在这种情况下并不明显。 @Pavel:您可以将循环分解为 if 语句,检查数字的每个字节(从左到右)是否非零,如果是,则将其附加到缓冲区。
【解决方案3】:

请注意,您将值保存到可变长度字节数组中。如果保存这些字节数组,还需要保存长度。

您可以从 BinaryWriter 看到受保护的函数 Write7BitEncodedInt,从 BinaryReader 看到 Read7BitEncodedInt。

这些函数在磁盘上保存正数的存储空间。一个数字 0-128 只需要一个字节。

Microsoft 使用这些函数在保存到 Stream 时存储/检索字符串长度前缀。

要使用这些函数,您可以创建自己的从 BinaryReader / BinaryWriter 派生的类。

【讨论】:

  • 是的,我还保存了长度,以便可以正确地将 byte[] 解码回整数。不幸的是,我不能使用 Write7BitEncodedInt,因为我需要完全按照我的问题中描述的格式写入整数值。
猜你喜欢
  • 2014-10-05
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 2020-07-05
  • 1970-01-01
  • 2017-03-17
相关资源
最近更新 更多