为什么80%的码农都做不了架构师?>>>
从学习编程的第一天,字符乱码乱码问题就开始困扰我。这个问题就像一个幽灵一样,时隐时现。很多时候,它并不是一个大问题,简单地去掉代码中出现的中文就可规避。但是躲了太久,我发现自己到现在也没有明白乱码问题的根源。今天,试图逐步弄清这个问题。
本文参考了这几篇博客
文件编码格式
UNICODE,GBK,UTF-8区别
Unicode字符编码规范
1. 初识字符编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码。
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。
不同的国家和地区制定了不同的标准,由此产生了GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
2. 常见的编码方式
2.1 中文
常见的简体中文编码方式有
-
GB2312(1980年)
收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 -
GBK1.0(1995年)
收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。 -
GB18030(2000年)
收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。
这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符
2.2 Unicode
Unicode 也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。
注意:Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容
Unicode的问题
1.ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了。
2.另一个更加严重的问题是,C语言使用'\0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉
2.3 UTF
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。所以可以认为,UTF是为传送unicode而想出来的“再编码”方式。
UTF-8
UTF-8就是以8位为单元对UCS进行编码。
UTF-8有点类似于Haffman编码
- 将Unicode编码为00000000-0000007F的字符,用单个字节来表示
- 将Unicode编码为00000080-000007FF的字符用两个字节表示
- 将Unicode编码为00000800-0000FFFF的字符用3字节表示
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89
UTF-16
UTF-16以16位为单元对UCS进行编码。
对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。所以就目前而言,可以认为UTF -16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以要考虑字节序的问题。
3. 字节序和BOM
3.1 字节序
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。如果将49写在前面,就是little endian。
假设int类型占4个字节,int类型的变量i被初始化为1。 以大端模式存储,其内存布局如下图:
以小端模式存储,其内存布局如下图:
如何编程判断是大端还是小端,参考这里
3.2 BOM
BOM是Byte Order Mark的缩写
在UCS 编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。