浅谈字节序(Byte Order)及其相关操作

 

最近在为Tokyo Tyrant写一个.NET客户端类库。Tokyo Tyrant公开了一个基于 TCP协议的二进制协议,于是我们的工作其实也只是按照协议发送和读取一些二进 制数据流而已,并不麻烦。不过在其中涉及到了 “字节序”的概念,这本是计算 机体系结构/操作系统等课程的基础,不过我还是打算在这里进行简单说明,并且 对.NET中部分类库在此类数据流处理时的注意事项进行些许记录与总结。

 

字节序(Byte Order)

 

说到程序间的通信,说到底便是发送数据流。我们一般把字节(byte)看作是 数据的最小单位。当然,其实一个字节中还包含8个比特(bit)──有时候我奇 怪为什么很多朋友会不知道bit或是它和byte的关系。当我们拿到一系列byte的时 候,它本身其实是没有意义的,有意义的只是“识别字节的方式”。例如,同样4 个字节的数据,我们可以把它看作是1个 32位整数、2个Unicode、或者字符4个 ASCII字符。

 

同样我们知道,在一个32位的 CPU中“字长”为32个bit,也就是4个byte。在 这样的CPU中,总是以4字节对齐的方式来读取或写入内存,那么同样这4个字节的 数据是以什么顺序保存在内存中的呢?例如,现在我们要向内存地址为a的地方写 入数据0x0A0B0C0D,那么这4个字节分别落在哪个地址的内存上呢?这就涉及到字 节序的问题了。

 

每个数据都有所谓的“有效位(significant byte)”,它的意思是“表示这 个数据所用的字节”。例如一个32位整数,它的有效位就是4个字节。而对于 0x0A0B0C0D来说,它的有效位从高到低便是0A、0B、0C及0D——这里您可以把它 作为一个256进制的数来看(相对于我们平时所用的10进制数)。

 

而所谓大字节序(big endian),便是指其“最高有效位(most significant byte)”落在低地址上的存储方式。例如像地址a写入0x0A0B0C0D之后,在内存中 的数据便是:

 

 

 

而对于小字节序(little endian)来说就正好相反了,它把“最低有效位 (least significant byte)”放在低地址上。例如:

 

 

对于我们常用的CPU架构,如Intel,AMD的CPU使用的都是小字节序,而例如 Mac OS以前所使用的Power PC使用的便是大字节序(不过现在Mac OS也使用Intel 的CPU了)。此外,除了大字节序和小字节序之外,还有一种很少见的中字节序( middle endian),它会以2143的方式来保存数据(相对于大字节序的1234及小字 节序的4321)。

关于字节序的详细说明,您可以参考Wikipedia里的Endianness条目。

相关.NET类库BinaryWriter和BinaryReader

在.NET框架操作数据流的时候,我们往往会使用BinaryWriter和BinaryReader 进行读写。这两个类中都有对应的WriteInt32或是ReadInt32方法,那么它们是如 何处理字节序的呢?从MSDN上我们了解到BinaryReader使用小字节序读取数据。 这意味着:

var stream = new MemoryStream(new byte[] { 4, 1, 0,  0 });
var reader = new BinaryReader(stream);
int i = reader.ReadInt32(); // i == 260

与之类似,自然BinaryWriter也是使用小字节序来写入数据。

 

 

BitConverter

 

有时候我们还会使用BitConverter来转化byte数组及一个32位整数(自然也包 括其他类型),这也是涉及到字节序的操作,那么它们又是如何处理的呢?与 BinaryWriter和BinaryReader的“固定策略”不同,BitConverter的行为是平台 相关的。

 

首先,BitConverter有一个只读静态字段IsLittleEndian,它表示当前平台的 字节序。由于我们为不同的CPU会安装不同的.NET类库,因此您现在如果通过.NET Reflector来查看这个字段会发现它被设置为一个常量true。那么接下来, BitConverter上的各个方便便会根据 IsLittleEndian的值产生不同行为了,例如 它的ToInt32方法:

 

public static unsafe int ToInt32(byte[] value, int  startIndex)

{

   // ...

 

   fixed (byte* numRef = &(value[startIndex]))

   {

     if ((startIndex % 4) == 0)

     {

       return *(((int*)numRef));

     }

     if (IsLittleEndian)

     {

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

     }

 

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

   }

}

 

显然,这里会根据IsLittleEndian返回不同的值。

 

判断当前平台的字节序

 

在.NET Framework中BitConverter.IsLittleEndian字段是一个常量,也就是 说它在编译期便写入了一个静态的值。那么我们如果想要通过代码来判断当前平 台的字节序,又该怎么做呢?其实这很简单:

 

static unsafe bool IsLittleEndian()

{

   int i = 1;

   byte* b = (byte*)&i;

   return b[0] == 1;

}

 

这里我们通过检查32位整数1的第一个字节来确定当前平台的字节序。当然, 我们也可以使用其他类型,例如:

 

static unsafe bool AmILittleEndian()

{

   // binary representations of 1.0:

   // big endian: 3f f0 00 00 00 00 00 00

   // little endian: 00 00 00 00 00 00 f0 3f

   // arm fpa little endian: 00 00 f0 3f 00 00 00  00

   double d = 1.0;

   byte* b = (byte*)&d;

   return (b[0] == 0);

}

 

这段代码来自mono的BitConverter类库,至于它为什么使用double而不是int ,我也不是很清楚。

 

Buffer.BlockCopy方法

 

.NET类库中自带一个Buffer.BlockCopy方法,它的作用是将一个数组的字节— —不是元素——复制到另一个数组中去。换句话说,一个长度为100的int数组经 过完整的复制后,就变成了长度为50的 long数组,因为一个int为4字节,而long 为8字节。从文档上看,Buffer.BlockCopy是与字节序相关的,也就是说,同样的 .NET 代码在字节序不同的平台上得到的结果可能不同。因此,我建议在使用这个 方法的时候多加小心。

面向特定字节序编程

 

我们知道,BitConverter的工作结果是和当前平台的字节序相关的,但是在很 多时候,尤其是根据某个公开的协议进行通信编程的时候,是需要固定一个字节 序的。例如Tokyo Tyrant便要求每个整数都以大字节序的方式来通信——无论是 发送还是读取。为了保证.NET代码的平台无关性,我们不能直接使用 BitConverter.GetBytes或ToInt32方法进行转化。那么我们该怎么办呢?最直观 的方法自然是手动进行转换:

 

static int ReadInt32(Stream stream)

{

   var buffer = new byte[4];

   stream.Read(buffer, 0, 4);

 

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

}

 

由于我们可以通过BitConverter.IsLittleEndian来得到当前平台的字节序, 我们也可以用它进行判断:

 

static int ReadInt32(Stream stream)

{

   var buffer = new byte[4];

   stream.Read(buffer, 0, 4);

 

   if (BitConverter.IsLittleEndian)

   {

     Array.Reverse(buffer);

   }

 

   return BitConverter.ToInt32(buffer, 0);

}

 

static void WriteInt32(Stream stream, int value)

{

   var buffer = BitConverter.GetBytes(value);

 

   if (BitConverter.IsLittleEndian)

   {

     Array.Reverse(buffer);

   }

 

   stream.Write(buffer, 0, buffer.Length);

}

 

此外,我们知道BinaryWriter和BinaryReader都是依据小字节序进行读写的, 因此我们也可以利用这点来读写数据流。要不,接下来就由您试试看如何?

 

 

 

 

 

 

转自:http://www.cnblogs.com/JeffreyZhao/archive/2010/02/10/byte-order-and- related-library.htm

http://www.bianceng.cn/Programming/net/201011/20192_4.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值