引起我对这个问题的思考的原因是因为unicode的编码格式UTF-8,UTF-8是没有字节顺序的。这个编码格式会把unicode字符集中的一个字符编码成1个或者多个字节。而UTF-8编码格式是经常用于网络传输的格式。如:XML文档大多数都是用的UTF-8格式存储的,这样利于网络传输。那么问题来了既然UTF-8把一个字符可能编码成了2个或3个字节,代表这个字符的2个或3个字节的编码为什么没有字节顺序。
下面我们再来看一个问题:
typedef struct _ALM_EVENT
{
UINT16 ID;
UINT16 Type;
INT8 Level;
} ALM_EVENT, *PALM_EVENT;
如果在小端字节的电脑上存储了上述结构体,其内存数据如下(按照地址从小到大的顺序依次列出各个字节):
【0x01,0x02,0x03,0x04,0x05】
那么把这段字节流发送到大端字节的电脑上的时候,如果要正确的把这个结构体解析出来的话,要把这段字节流的顺序变成如下的顺序:
【0x02,0x01,0x04,0x03,0x05】
这里我当时有个疑问,为什么不是整个结构体的字节顺序都要颠倒过来变成如下的字节顺序:
【0x05,0x04,0x03,0x02,0x01】
而是仅仅只把其中的两个UINT16变量的内部字节顺序改变了就行。
经过思考发现,只有多个字节的各个位(bit)是有关系的,或者说要解析出这个多字节时,它的各个位(bit)是要按照一定的顺序排列的。
像UINT16这种2个字节的编码格式:0111000010001111,要把这个16位的编码放在内存里的话是要2个字节的,而这2个字节里的各个位(bit)要按照0111000010001111(从高位到底位)这个顺序来排列和解析。也就是说其实UINT16这个数据是不以字节区分的,它的最小单元为位(bit),只不过它在内存中占用的空间大小为2个字节(内存空间最小的分配单元为字节)。
由此可以得出一个结论:如果编码后的2进制流是大于8位(bit)的,并且整个2进制流是有高低位之分的,解析该2进制流时是需要把各个bit按照一定的顺序排列的,那么存储在内存里的这个多字节的编码就是有字节顺序的(也就是说字节有高字节和低字节之分),存储和解析时就是需要区分大端和小端的。
一般情况下,在内存里各个字节之间有高低位之分的好像目前接触到的就只有内置的各种整形类型了,如:int,uint,short,ushort等等。
现在就可以解释前面提出的问题了,为什么不需要把整个结构体的字节顺序都颠倒过来。因为结构体只是代表一段内存的布局。如上的结构体只是告诉我们,头两个字节是UINT16,3和4两个字节是存储的UINT16,最后一个字节是存储的INT8,而这3个数据类型之间没有任何关系,他们的各个bit之间没有任何关系,这3个数据是相互独立的。
所以按照地址从小到大的顺序的话,这个结构体的3个变量的顺序是不会变的(相对地址没变,布局没变)。
接下来就说说UTF-8为什么字符编码成了多个字节是没有字节顺序的。
Unicode编码(十六进制)
|
UTF-8 字节流(二进制)
|
00000000 - 0000007F
|
0xxxxxxx
|
00000080 - 000007FF
|
110xxxxx 10xxxxxx
|
00000800 - 0000FFFF
|
1110xxxx 10xxxxxx 10xxxxxx
|
00010000 - 001FFFFF
|
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
00200000 - 03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
04000000 - 7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |