为什么 utf8没有字节序,utf16utf32有字节序?

Posted wangjun5159

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么 utf8没有字节序,utf16utf32有字节序?相关的知识,希望对你有一定的参考价值。

字节序

先看字节序的定义,援引维基百科

Endianness is the sequential order in which bytes are arranged into larger numerical values when stored in memory or when transmitted over digital links.

简单来说,字节序就是字节之间的顺序,当传输或者存储时,如果数字超过1个字节,需要指定字节间的顺序。字节序用英语说就是byte order mark,简称bom。

由来

比如,java中的int类型,它占用4个字节,比如数值1吧,翻译为十六进制就是0x[00] [00] [00] [01],最左边是最高位(most significant bit)、最右边是最低位(least significant bit),内存地址从低到高依次排列为地址1、地址2、地址3、地址4,但计算机处理时有两种处理方法,第一种是先放最高位、再放次最高位…,这种因为最高位在前,所以叫大端(big endian),简称BE;第二种是先放最低位、再放次最低位…,依次排开,这种最低位在最前边,所以叫小端(little endian)简称LE
如下

地址1地址2地址3地址4大端/小端
00000001大端
01000000小端

现在,我们就把int 1,从客户端发到服务器端,既然是网络传输肯定是传字节流,好了,客户端恰巧是大端,发送的数据就是[00]、[00]、[00]、[01],但接收端的计算机恰好是小端,接收到的顺序仍然是[00]、[00]、[00]、[01],但因为是小端,所以按照小端的规则解析后就变为0x[01]、[00]、[00]、[00],看见了吧,出错了。所以在传输时,有必要说明采用的哪种字节序,一般都是在字节数组的前边插入bom。
维基百科的图

utf-8、utf-16、utf-32、bom

  • utf-8在实际应用中,一般来说没有bom,因为utf-8的编码单元是字节,没有超过1个字节,所以就不存在字节序问题。当然,如果你非要加bom也无所谓。
  • utf-16是以16-bit为编码单元,因为编码单元是16-bit,超过1个字节,这个时候就有字节序问题,所以最好指定bom,有utf-16LE、utf-16BE。
  • utf-32是定长编码方案,它的编码单元是32-bit,同utf-16,编码单元都超过1个字节,所以需要指定bom,utf-32BE、utf-32LE
  • 简单概括一下,其实utf-8、utf-16、utf-32都可以没有bom,只要传送端跟接收端一致即可,或者即使操作系统不一致,通过程序转为一致也可以。
    这个地方有点绕,因为很多人说,不对呀,汉字utf-8编码是3个字节,明明超过1个字节了,为什么说没有字节序问题,这是因为utf8的编码单元是1字节,这3个字节的顺序是确定的,拿出任何一个编码单元都没超过1字节,所以不存在字节序问题。
    而utf-16,编码单元是16-bit,也就是2个字节,一个字符对应1个16-bit编码单元,这2个字节,在编码单元内部排列不受编码方案约束。

实例

windows的记事本,utf8默认是带bom的

理解了上述内容,那么会理解很多东西,比如windows的记事本保存时,为什么要区分unicode 和unicode big endian了,当然了,这里的unicode就是utf-16。

另外还有一点 windows记事本的utf-8默认是带bom的,我们可以验证一下,用windows记事本保存为utf8,然后用sublime text打开,看一下编码方案

java

public void testEncode() throws UnsupportedEncodingException 

        String s = "你";
        //将字符编码
        byte[] bytesUTF8 = s.getBytes("utf-8");
        byte[] byteUTF16 = s.getBytes("utf-16");
        byte[] bytesUTF32 = s.getBytes("utf-32");
        System.out.println(Arrays.toString(convertByte2Int(bytesUTF8))); // [228, 189, 160]
        System.out.println(Arrays.toString(convertByte2Int(byteUTF16))); // [254, 255, 79, 96] 
        System.out.println(Arrays.toString(convertByte2Int(bytesUTF32))); // [0, 0, 79, 96],
    

    private int[] convertByte2Int(byte[] arr) 
        int[] dest = new int[arr.length];
        for (int i = 0; i < arr.length; i++) 
            byte ele = arr[i];
            dest[i] = ele & 0xFF;
        
        return dest;
    

第一个输出,[228, 189, 160],因为“万”字,在unicode的基本平面,占3个字节,故长度为3。utf-8一般来说没必要有bom,所以也没有bom,只有表示字符的三个字节。
第二个输出,[254, 255, 79, 96] ,“万”字,在unicode的基本平面,所以utf-16中基本平面字符占用2个字节,[79,96];而[245,255],这俩是BOM,用十六进制表示就是[FE,FF],这就是java在utf-16中添加的bom了,FEFF,是unicode规定的BOM。
第三个输出,[0, 0, 79, 96],utf-32是定长编码,所有字符占4个字节,这里没有添加bom。

综上所述,编码方案有无BOM皆可,只要传送端跟接收端一致即可,或者即使操作系统不一致,通过程序转为一致也可以。如果理解了上述知识,可以看一下java的class文件的bom,最开始是CAFE BABE,这就是java往文件中添加的BOM了

参考

Endianness

以上是关于为什么 utf8没有字节序,utf16utf32有字节序?的主要内容,如果未能解决你的问题,请参考以下文章

字节序的问题,为啥GBK和UTF-8没有字节序问题,而UTF-16就有?

UTF16和UTF8啥区别?

C++处理UTF8编码的字符串

UTF8和UTF16

vs2010 汉字utf8问题

字符串与bytes