将整数转换为字节数组

84

我认为.NET有一种简单的转换方法,可以将int类型转换为byte数组?我进行了快速搜索,发现所有的解决方案都是逐个字节进行位掩码 / 移位操作,就像“好旧的日子”一样。 难道没有ToByteArray()方法吗?

6个回答

142

2020年更新 - 现在应该优先使用BinaryPrimitives而不是BitConverter。它提供了特定于字节序的API,并且分配更少的内存。


byte[] bytes = BitConverter.GetBytes(i);

需要注意的是,你可能需要检查BitConverter.IsLittleEndian来确定字节序列的顺序!

如果你需要频繁地进行此操作,你可能希望避免所有这些短暂的数组分配,并通过位移运算(>> / <<)或者使用不安全代码自己编写。位移运算还具有一个优点,它们不受您平台的字节序影响,你总是能按照预期的顺序获得字节。


2
+1:为了清晰起见,我建议用“大端”替换“[...]按您期望的顺序”。 - Ani
为什么 BitConverter.GetBytes(250) 返回 [250,0,0,0] 而不是 [250]Byte.MaxValue == 255,对吗? - user4584267
@Tobi 因为你调用了 GetBytes(int) 方法 - 所以会返回 4 个字节,并且你的 CPU 是小端序。因此,使用 GetBytes(byte) 方法是没有意义的。 - Marc Gravell
1
@Tobi 关于 GetBytes(256) 的问题:你不会得到 255,1,因为它是固定宽度表示 - 字节。256 的小端(你的 CPU)固定宽度 32 位表现形式才是你将得到的结果。 - Marc Gravell
1
@Timeless 通常情况下unsafe - 但具体取决于您要做什么 - 最好的方法是实现两次并测试其中的1000万个,对它们进行计时。 - Marc Gravell
显示剩余3条评论

40

马克的回答当然是正确的。但由于他提到移位运算符和不安全代码作为替代方案,我想分享一个不太常见的替代方案。使用带有 Explicit 布局的结构体。这在原理上类似于 C/C++ 中的 union

以下是一个示例结构体,可用于获取 Int32 数据类型的组件字节,好处是它是双向的,可以操纵字节值并查看对 Int 的影响。

  using System.Runtime.InteropServices;

  [StructLayout(LayoutKind.Explicit)]
  struct Int32Converter
  {
    [FieldOffset(0)] public int Value;
    [FieldOffset(0)] public byte Byte1;
    [FieldOffset(1)] public byte Byte2;
    [FieldOffset(2)] public byte Byte3;
    [FieldOffset(3)] public byte Byte4;

    public Int32Converter(int value)
    {
      Byte1 = Byte2 = Byte3 = Byte4 = 0;
      Value = value;
    }

    public static implicit operator Int32(Int32Converter value)
    {
      return value.Value;
    }

    public static implicit operator Int32Converter(int value)
    {
      return new Int32Converter(value);
    }
  }

现在可以按以下方式使用上述内容

 Int32Converter i32 = 256;
 Console.WriteLine(i32.Byte1);
 Console.WriteLine(i32.Byte2);
 Console.WriteLine(i32.Byte3);
 Console.WriteLine(i32.Byte4);

 i32.Byte2 = 2;
 Console.WriteLine(i32.Value);

当然,不可变性警察可能对最后一种可能性不感兴趣 :)


语句“Byte1 = Byte2 = Byte3 = Byte4 = 0;”是冗余的,因为随后的值赋值将覆盖它们。 这正是驱动编译器优化器编写人员发疯的别名类型。 联合体很优雅,因为它通过内存通道将多个写操作折叠成一个写操作。 对于非常频繁的情况,使用结构体也避免了数组分配。 - Pekka
@Pekka,编译器强制要求您初始化结构的所有成员,并且不考虑字节被int覆盖的事实,这就是为什么需要初始化,即使是冗余的也是必需的。 - Chris Taylor
这个解决方案不也容易受到大小端问题的影响吗?如果你在一个大端系统上,"Value"字段的内容会被反转,对吧? - eikuh
@eikuh,确实,Bitconverter.IsLittleEndian可以帮助确定正确的字节读取顺序。但是,就像我说的那样,这不是正确的答案,我只是分享它,因为在当时这种技术并不常见。 - Chris Taylor
@ChrisTaylor,你说这不是正确的答案的地方在哪里? - eikuh
显示剩余5条评论

2

这可能与主题无关,但如果您需要序列化大量的原始类型或POD结构,则Google Protocol Buffers for .Net可能对您有用。这解决了Marc提到的字节序问题,以及其他一些有用的功能。


2

如果您从谷歌来到这里

对于旧问题的替代答案提到了John Skeet的库,该库具有让您直接将原始数据类型写入带有索引偏移量的byte[]的工具。如果需要性能,则远比BitConverter更好。

在此处讨论旧线程

John Skeet的库在这里

只需下载源代码并查看MiscUtil.Conversion命名空间。 EndianBitConverter.cs会为您处理所有内容。


1

使用MemoryMarshalSpan进行堆分配释放

int messageLen = 1111;
Span<byte> messageTypeBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref messageLen, 1));

BenchmarkDotnet

|            Method |      Mean |     Error |    StdDev | Allocated |
|------------------ |----------:|----------:|----------:|----------:|
| MemoryMarshalTest | 0.0023 ns | 0.0017 ns | 0.0014 ns |         - |

0

这里的大多数答案要么是“不安全”的,要么不支持LittleEndian。BitConverter不支持LittleEndian。 因此,在here的示例基础上(请参见PZahra发布的帖子),当BitConverter.IsLittleEndian == true时,我通过反向读取字节数组来制作了一个LittleEndian安全版本。

void Main(){    
    Console.WriteLine(BitConverter.IsLittleEndian); 
    byte[] bytes = BitConverter.GetBytes(0xdcbaabcdfffe1608);
    //Console.WriteLine(bytes); 
    string hexStr = ByteArrayToHex(bytes);
    Console.WriteLine(hexStr);
}

public static string ByteArrayToHex(byte[] data) 
{ 
   char[] c = new char[data.Length * 2]; 
   byte b; 
  if(BitConverter.IsLittleEndian)
  {
        //read the byte array in reverse
        for (int y = data.Length -1, x = 0; y >= 0; --y, ++x) 
        { 
            b = ((byte)(data[y] >> 4)); 
            c[x] = (char)(b > 9 ? b + 0x37 : b + 0x30); 
            b = ((byte)(data[y] & 0xF)); 
            c[++x] = (char)(b > 9 ? b + 0x37 : b + 0x30); 
        }               
    }
    else
    {
        for (int y = 0, x = 0; y < data.Length; ++y, ++x) 
        { 
            b = ((byte)(data[y] >> 4)); 
            c[x] = (char)(b > 9 ? b + 0x37 : b + 0x30); 
            b = ((byte)(data[y] & 0xF)); 
            c[++x] = (char)(b > 9 ? b + 0x37 : b + 0x30); 
        }
    }
    return String.Concat("0x",new string(c));
}

它返回这个:

True
0xDCBAABCDFFFE1608

这是进入字节数组的确切十六进制。


不过它并不是从一个整数开始的。更重要的是,在_read_上进行字节序检查更加合理,这样你就可以确定数组中的数据类型。 - Nyerguds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接