浅谈C#和Java的大小端模式

浅谈C#和Java的大小端模式

0. 前言

02

今天在现场调试时,遇到698协议解析出来的数据和实际数据不一致的问题,经过一番排查,发现是大小端模式的问题,由于C#和Java的大小端模式不同,导致解析出来的数据和实际数据不一致。

然后自己手动实现了一小部分协议,因为之前一直实在做应用层的开发,对于底层的东西了解的不是很多,导致写代码的时候理所当然的认为C#和Java的内存模型是一样的。

结果后来发现,C#和Java的内存模型是不一样的,C#是小端模式,Java是大端模式。

这个问题困了我一上午,所以简单了解了一下大小端模式,记录一下。

1. 什么是大小端模式

在计算机中,数据在内存中的存储方式有两种,一种是大端模式,一种是小端模式。大端模式是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放。小端模式是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

01

以0x12345678为例,这是一个4字节的整数值。在大端模式中,这个整数的高字节0x12存储在低地址中,而低字节0x78存储在高地址中。在小端模式中,这个整数的低字节0x78存储在低地址中,而高字节0x12存储在高地址中。

具体来说,假设这个整数存储在内存地址0x100处,那么在大端模式中,它的内存布局如下:

地址 0x100 0x101 0x102 0x103
数据 0x12 0x34 0x56 0x78
2进制数据 0001 0010 0011 0100 0101 0110 0111 1000

而在小端模式中,它的内存布局如下:

地址 0x100 0x101 0x102 0x103
16进制数据 0x78 0x56 0x34 0x12
2进制数据 0111 1000 0101 0110 0011 0100 0001 0010

可以看到,在大端模式中,0x12存储在低地址中,而在小端模式中,0x78存储在低地址中。这两种方式的区别在于字节的排列顺序,但它们都可以正确地表示相同的整数值。

2. 为什么要有大小端模式

计算机有大小端模式是因为在内存中存储数据的方式不同,而这种存储方式是由处理器的设计决定的。
在计算机中,数据是以二进制形式存储的。在内存中,一个数据项通常占用多个字节,例如一个整数通常需要4个字节来存储。对于多字节数据,处理器需要决定如何排列这些字节在内存中,以便正确地表示这个数据项。
在大端模式中,数据的高字节存储在低地址中,而数据的低字节存储在高地址中。这类似于我们在写十进制数字时的顺序,例如数字1234中的“1”是最高位,而“4”是最低位。因此,大端模式也被称为“大尾端模式”。
在小端模式中,数据的低字节存储在低地址中,而数据的高字节存储在高地址中。这种方式看起来更加直观,因为我们在阅读数字时通常也是从低位到高位依次阅读的。因此,小端模式也被称为“小尾端模式”。
虽然大端模式和小端模式的实现不同,但它们可以互相转换。这个问题的重要性在于,不同的系统使用不同的字节序,如果在不同的系统之间传递数据,则必须考虑字节序的问题。

3. 大小端模式的应用

3.1 C#中的大小端模式

C#中的BitConverter类提供了一些方法,可以用来将数据类型转换为字节数组,或者将字节数组转换为数据类型。这些方法都是静态方法,所以不需要实例化BitConverter类就可以直接使用。

1
2
3
4
//将一个整数转换为字节数组
byte[] bytes = BitConverter.GetBytes(123);
//将一个字节数组转换为整数
int num = BitConverter.ToInt32(bytes, 0);

在C#中,BitConverter类提供了两种大小端模式,一种是BigEndian,一种是LittleEndian。默认情况下,BitConverter类使用的是LittleEndian模式,也就是小端模式。如果要使用大端模式,可以通过BitConverter.IsLittleEndian属性来判断当前的大小端模式,然后通过BitConverter类的静态方法来转换数据。

1
2
3
4
5
6
//判断当前的大小端模式
if (BitConverter.IsLittleEndian)
{
//如果是小端模式,将字节数组反转
Array.Reverse(bytes);
}

3.2 Java中的大小端模式

在Java中,我们可以通过ByteBuffer类来实现字节序的转换。ByteBuffer类提供了两种大小端模式,一种是BigEndian,一种是LittleEndian。默认情况下,ByteBuffer类使用的是BigEndian模式,也就是大端模式。如果要使用小端模式,可以通过ByteBuffer类的静态方法来转换数据。

1
2
3
4
5
6
7
//将一个整数转换为字节数组
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(123);
byte[] bytes = buffer.array();
//将一个字节数组转换为整数
ByteBuffer buffer = ByteBuffer.wrap(bytes);
int num = buffer.getInt();

在Java中,我们可以通过ByteOrder类来判断当前的大小端模式,然后通过ByteBuffer类的静态方法来转换数据。

1
2
3
4
5
6
//判断当前的大小端模式
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
{
//如果是小端模式,将字节数组反转
ArrayUtils.reverse(bytes);
}

3.3 网络字节序

网络字节序是TCP/IP协议规定的一种数据表示格式,它与具体的操作系统、处理器无关,从而可以保证数据在不同主机之间传输时能够被正确解释。在网络字节序中,数据的高字节存储在低地址中,而数据的低字节存储在高地址中,这与大端模式相同。因此,网络字节序也被称为“大尾端模式”。

3.3.1 C#中的网络字节序

在C#中,可以通过IPAddress.HostToNetworkOrder方法将数据转换为网络字节序,也可以通过IPAddress.NetworkToHostOrder方法将数据转换为主机字节序。

1
2
3
4
//将一个整数转换为网络字节序
int num = IPAddress.HostToNetworkOrder(123);
//将一个整数转换为主机字节序
int num = IPAddress.NetworkToHostOrder(123);

值得注意的是,IPAddress.HostToNetworkOrder方法和IPAddress.NetworkToHostOrder方法只能用于16位整数和32位整数,不能用于64位整数。

在C#中,HostToNetworkOrder方法和NetworkToHostOrder方法分别用于将主机字节顺序(Host Order)转换为网络字节顺序(Network Order)和将网络字节顺序转换为主机字节顺序。在这里,主机字节顺序指的是当前计算机所使用的字节序,而网络字节顺序则指的是大端字节序(Big Endian)。

因此,HostToNetworkOrder方法实际上是将当前计算机使用的小端字节序(Little Endian)转换为网络字节序(Big Endian),而NetworkToHostOrder方法则是将网络字节序转换为当前计算机使用的小端字节序。这是因为在网络通信中,使用的是大端字节序,而不同计算机的字节序可能不同,因此需要进行字节序的转换。

需要注意的是,这些方法的命名可能有些让人困惑,因为它们的名称中并没有明确指出字节序的类型。但是,在.NET Framework的文档中,它们被描述为将16位或32位的有符号整数从主机字节顺序转换为网络字节顺序或从网络字节顺序转换为主机字节顺序。因此,可以根据这一描述来判断这些方法所处理的字节序。

3.3.2 Java中的网络字节序

在Java中,默认情况下,ByteBuffer类使用的是BigEndian模式,也就是大端模式。因此,如果要将数据转换为网络字节序,只需要将数据写入ByteBuffer对象,然后将ByteBuffer对象转换为字节数组即可。

1
2
3
4
5
6
7
//将一个整数转换为网络字节序
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(123);
byte[] bytes = buffer.array();
//将一个字节数组转换为整数
ByteBuffer buffer = ByteBuffer.wrap(bytes);
int num = buffer.getInt();

4. 总结

在C#和Java中,我们都可以通过BitConverter类和ByteBuffer类来实现字节序的转换。在C#中,BitConverter类提供了两种大小端模式,一种是BigEndian,一种是LittleEndian。默认情况下,BitConverter类使用的是LittleEndian模式,也就是小端模式。在Java中,ByteBuffer类提供了两种大小端模式,一种是BigEndian,一种是LittleEndian。默认情况下,ByteBuffer类使用的是BigEndian模式,也就是大端模式。在C#和Java中,我们都可以通过BitConverter类和ByteBuffer类来判断当前的大小端模式,然后通过BitConverter类和ByteBuffer类的静态方法来转换数据。

5. 参考资料

[1] C#中的BitConverter类

[2] Java中的ByteBuffer类