【问题标题】:What is the idiomatic c# for unpacking an integer from a byte array?从字节数组中解包整数的惯用 c# 是什么?
【发布时间】:2009-05-18 20:42:19
【问题描述】:

我正在解析二进制文件格式。它以自然适合 c# 的 uint 类型的方式使用四个字节对整数进行编码。

实现此功能的最 C#/惯用方式是什么:

uint ReadUint(byte[] buffer);

假设缓冲区包含 4 个元素。完整的答案可能会考虑文件中由小/大端假设引起的一些常见字节排序,并记录它选择解析的那些。

【问题讨论】:

    标签: c# parsing


    【解决方案1】:

    最基本的(但有点危险的re endianness)是:

    return BitConverter.ToUInt32(buffer, 0);
    

    除此之外,移位也可以(根据您自己的回复)- 或者您可以使用 Jon 的 EndianBitConverter in MiscUtil,它负责处理翻译。

    (编辑)

    我在 protobuf-net 中使用的 little-endian 位移版本与您的版本几乎相同 - 我只是按升序阅读它们并使用按位(非数字)加法:

    return ((uint)buffer[0])
            | (((uint)buffer[1]) << 8)
            | (((uint)buffer[2]) << 16)
            | (((uint)buffer[3]) << 24);
    

    【讨论】:

    • 我是否认为在“大端”.net 平台上运行它会中断,因为它会尝试解析二进制格式并假设大端字节顺序?
    • 哪个“这个”?由于字节顺序,BitConverter.ToUInt32 将在安腾(IA64)上返回不同的值,这可能意味着一个问题,是的。移位方法不受系统字节序的影响。
    • 我的评论是在您使用位移解决方案之前发布的 :-)
    【解决方案2】:

    我通常会为此使用 BitConverter 类。在您的情况下,BitConverter.ToUInt32() 方法。

    【讨论】:

    • 您可能是指 ToUInt32(参见 OP)
    【解决方案3】:

    这个回复实际上是一个扩展评论(因此是 wiki),比较了 BitConverter 的性能和使用 + 与 | 的位移;它仅适用于微优化!

    结果第一:

    BitConverter: 972ms, chk=1855032704
    Bitwise: 740ms, chk=1855032704
    ReadLength: 1316ms, chk=1855032704
    

    或者如果调整为允许非零基偏移量的结果:

    BitConverter: 905ms, chk=1855032704
    Bitwise: 1058ms, chk=1855032704
    ReadLength: 1244ms, chk=1855032704
    

    还有代码:

    using System;
    using System.Diagnostics;
    static class Program
    {
        static void Main()
        {
            byte[] buffer = BitConverter.GetBytes((uint)123);
            const int LOOP = 50000000;
            uint chk = 0;
            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                chk += BitConverter.ToUInt32(buffer, 0);
            }
            watch.Stop();
            Console.WriteLine("BitConverter: " + watch.ElapsedMilliseconds
                + "ms, chk=" + chk);
    
            chk = 0;
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                chk += Bitwise(buffer);
            }
            watch.Stop();
            Console.WriteLine("Bitwise: " + watch.ElapsedMilliseconds
                + "ms, chk=" + chk);
    
            chk = 0;
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                chk += ReadLength(buffer);
            }
            watch.Stop();
            Console.WriteLine("ReadLength: " + watch.ElapsedMilliseconds
                + "ms, chk=" + chk);
    
            Console.ReadKey();
        }
        static uint Bitwise(byte[] buffer)
        {
            return ((uint)buffer[0])
                | (((uint)buffer[1]) << 8)
                | (((uint)buffer[2]) << 16)
                | (((uint)buffer[3]) << 24);
        }
        static uint ReadLength(byte[] buffer)
        {
            uint result = ((uint)buffer[3]) << 24;
            result += ((uint)buffer[2]) << 16;
            result += ((uint)buffer[1]) << 8;
            result += buffer[0];
            return result;
        }
    }
    

    【讨论】:

      【解决方案4】:

      作为一个来自 C 的人,这就是我目前实现此功能的方式:

      static uint ReadLength(byte[] buffer)
      {
          uint result = ((uint) buffer[3]) << 24;
          result |= ((uint) buffer[2]) << 16;
          result |= ((uint) buffer[1]) << 8;
          result |= buffer[offset];
          return result;
      }
      

      这会解析维基百科声称在 i386/Vista 上运行的 .net 实现上以 little-endian 方式布局的格式

      【讨论】:

      • 注意按位 |将比数字+简单...请参阅我的(更新的)答案作为示例。
      • 为什么会|比 + 更“简单”?
      • 我的理解是,按位运算涉及的 CPU 工作量比数学要少,因为它只是应用位掩码。在“已检查”上下文中可能对 +(但不是 |)进行额外的溢出检查等(请注意,默认情况下 C# 是“未检查”)。
      • 我衷心建议将 + 更改为 |。这(对我来说)完全不直观,因为主要操作是将两个值按位“连接在一起”,它不是数字加法。使用 + 提出了这实际上是一个加法的问题,这(对我来说)非常令人困惑。如果在新位的位置设置了位,则函数将因进位而失败,这(对我而言)表明 + 是错误的操作。
      • 我从后面的回答中看到 |在 c# 中显然比 + 快。在我看来,这只是某种算术,所以 + 是我第一次碰巧实现它的方式。我已将此答案编辑为 |不过,为了那些将来阅读本文的人的利益。
      【解决方案5】:
      byte[] ba = new byte[]{ 0x10, 0xFF, 0x11, 0x01 } ;
      var ui = BitConverter.ToUInt32(ba, 0);
      

      使用BitConverter Class

      【讨论】:

        【解决方案6】:

        最简单的方法就是

        int val  = System.BitConverter.ToInt32(buffer, 0);
        

        这使用当前系统字节序,这可能是也可能不是您想要的。

        【讨论】:

          【解决方案7】:

          假设您想读取它们的流(正如您的代码所建议的那样) 我会说这非常接近事实上的标准方式:

          MemoryStream ms = new MemoryStream(new byte[100]);
          BinaryReader br = new BinaryReader(ms);
          uint q = br.ReadUInt32();
          

          【讨论】:

          • 这里也一样。我实际上更喜欢玩弄。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-02
          相关资源
          最近更新 更多