本文全文摘抄自此篇文章,原文链接如下:原文参考链接
我们在开发中是不是会经常遇到这样的问题,比如你在VS中写的C语言代码和中文注释,通过使用其它编辑器如NodePad++等打开的时候发现C语言程序代码部分可以正常显示,但是中文注释却成了一堆乱码。
这其实是因为VS和NodePad++的编码方式不一样,因为大部分的编码都是兼容ASCII编码的,而C语言程序都是英文字符,采用的是ASCII编码所以可以正常显示出来。但是中文字符却不同了,比如在内存中同样的编码0xb0a1使用不同的编码方式去进行解码,得到的可能就是不同的汉字。
在计算机世界中只有0、1两种数字。不论英文、中文还是其它语言字符在计算机中都是以0和1的方式进行存储。因此想要把文字字符存储到计算机中,就需要使用0和1组成的特定序列来表示对应的文字。编码就是规定特定的01序列来表示文字的过程,编码表示了文字字符在计算机中的存储形式。
我们在计算机中经常见到的文字、数字、英文字母、图片以及音视频之类的,这些信息在计算机中都是以二进制的形式进行存储。因为内存条是由电子元器件组成的,他们只有高电平和低电平即1和0两种状态。实际上,我们常说的十进制、八进制以及char、int、float、bool等数据类型这些概念都是对于程序员而言的。
比如十进制、十六进制只是一个数字表现形式的不同,逢十进一或逢十六进一。对于数据类型,这些数据类型是对内存条的解释不同,数据类型表明了这段内存所能表示的数据范围不同:比如char类型占一个字节,便是的数据范围是0-255。有时候我们在程序中会对变量类型进行转换,比如十进制与十六进制之间的相互转换,char与int类型之间的转换。这些转换都只是对内存的解释,主要表现在所占字节的大小以及数据的表示范围。但是无论进行怎样的转换,同一个数据不会因为类型转换而改变内存中的实际数据。例如字符'b'在char类型下占一个字节00000001,转为int之后为0000 0000 0000 0001。
数据类型只是对内存的解释,而真正决定他们在计算机中存储形式的是编码,编码指出了一个数据在计算机中以怎样的01序列进行存储。
因此我们可以得出一下结论:
字符编码规定了01序列在计算机中怎样进行存储。
ASCII采用8位二进制进行编码(1字节),用于表示控制字符、英文字符、数字字符。因为使用8位二进制进行编码,所以ASCII编码只能表示256个字符,编码范围为0-255。常见的ASCII编码如下:
字符 | 十进制形式 | 十六进制形式 |
---|---|---|
0~9 | 48~57 | 0x30~0x39 |
A~Z | 65~90 | 0x41~0x5a |
a~z | 97~122 | 0x61~0x7a |
不管ASCII码的十进制形式还是十六进制形式,它都是一种解释性的概念,它们在计算机中都是以二进制的形式进行存储,且存储的数据是不变的。这种采用8位二进制来表示或存储字符的过程就叫做编码。这些用ASCII码表示的字符集合就是ASCII字符集。
在英文世界中,使用26个字母就可以拼写出所有的英文单词,每个字母都是一个字符。所以,用8位的ASCII码就可以对整个英文世界进行编码。
英文编码可以使用字母编码来代替,这是因为所有的英文单词都可以拆分成26个英文字母的组合。而中文就不一样了,中文中一个字就是一个独立的整体,不可以进行拆分,所以只能按照字来进行编码。中文汉字成千上万,如果仅用8位的ASCII码来进行编码是明显不够的。
GB2312总共覆盖了6763个常用汉字,GB2312标准把ASCII码表127后面的扩展字符集去掉,并规定小于127(0x7F)的编码按照ASCII标准进行解码,当出现连续两个大于127(0x7F)的编码时,这两个连续的大于127(0x7F)的编码表示一个汉字,第一二个字节都是用0xA1~0xFE进行编码。其中ASCII码中原有的数字字符、英文字符、标点等称为半角字符,大于127(0x7F)的相应字符编码称为全角字符。
GB2312解码规则如下:当使用GB2312编码标准的时候,给定一串字符编码,按照字节进行检测,首先检测每个字节的大小,如果字节值小于0x7F,则采用ASCII标准进行解码;如果连续两个字节的值都大于0x7F,就把这两个字节视为一个整体,使用GB2312标准解码。例如:
0x61 | 0xb0 | 0xa1 | 0x61 |
从第一个字节开始检测,0x61小于0x7f,使用ASCII标准进行解码,表示英文字符‘a’;第二个字节0xb0大于0x7f,第三个字节0xa1大于0x7f,连续两个字节大于0x7f,把它们视为一个整体使用GB2312标准进行解码,表示中文字符“啊”;第四个字节0x61小于0x7f,用ASCII标准进行解码,他表示英文字符'a'。综上所述可得到如下解码内容:
0x61 | 0xb0 | 0xa1 | 0x61 |
a | 啊 | a |
GBK编码在GB2312的基础上又增加了14240个汉字、生僻字等。按照GB2312的编码方式,两个字节已经不够用了,这时候GBK编码制定了新标准:只要出现一个大于127(0x7F)的字节,那么这个字节和它后面的一个字节共两个字节表示一个汉字,这样做的好处是可以同时兼容ASCII和GB2312。
GBK解码规则是:当使用GBK编码标准时,给定一串字符编码,按照字节进行检测,首先检测每个字节的大小,如果字节值小于127(0x7F),就用ASCII标准进行解码;如果遇到一个大于127(0x7F)的字节,就把该字节和它后面的一个字节连在一起用GBK标准进行解码,然后继续从下一个字节开始遍历。例如:
0x61 | 0xb0 | 0x56 | 0x62 |
从第一个字节开始检测,0x61小于0x7f,采用ASCII标准进行解码,它表示英文字母‘a’;第二个字节0xb0大于0x7f,那么把0xb0和0x56视为一个整体表示一个字符并采用GBK规则进行解码;然后从0x62继续检测,解码内容如下:
0x61 | 0xb0 | 0x56 | 0x62 |
a | 癡(chi) | b |
每个国家和地区都有一套自己的文字,不同的文字系统就要使用不同的编码标准。这样就导致同一个二进制编码在不同的编码标准下可能代表了不同的字符,这样各个编码标准之间的不兼容就导致使用起来非常的不方便。国际标准化组织ISO,将全球所有的语言所使用的的字符、符号、文字统一进行编码,每个字符指定唯一一个编号与之对应,并保持了ASCII码编号不变。字符的编号从0x000000~0x10ffff,改编号集称为UCS,一般也叫作Unicode。
Unicode字符集仅仅只是对所有的字符进行编号,并没有指定这些编号的编码规则,所以后来出现了各种Unicode的编码规则,典型的Unicode编码规则如UTF-8、UTF-16、UTF-32等。
UTF-32用32位(4字节)对Unicode字符集进行编码。编码时,Unicode字符集中的每个字符都使用4字节表示,直接把字符对应的Unicode编码转换为二进制进行存储。而正是因为UTF-32采用4字节的编码方式,所有它不兼容ASCII编码,导致使用ASCII编码规范写的程序使用UTF-32打开时会显示乱码。
ASCII编码 | Unicode编码 | UTF-32编码 | GBK编码 | |
字符“A” | 0x41 | 0x00000041 | 0x00000041 | 0x41 |
字符“啊” | 0x0000554a | 0x0000554a | 0xb0a1 |
UTF-16采用16位(2字节)或32位(4字节)对Unicode字符进行编码。对Unicode字符集中0~65535的字符使用2字节编码,将每个字符的编号直接转换为2进制的二进制数0x0000~0xffff。而Unicode字符集在0xd800~0xdbff区间内的编号不表示任何字符集,UTF-16用这段编号与Unicode字符集中大于0xffff的字符编号进行映射,得到扩展的4字节编码。UTF-16也不兼容ASCII编码。
UTF-16解码的时候,按两个字节去检测,如果这两个字节的地址不在0xd800~0xdfff之间,就说明是双字节编码的字符,使用双字节进行解码;否则以4字节进行解码。
UTF-8用1,2,3,4个字节对Unicode字符集进行编码,每个字符根据自己的编号范围进行相应的编码。它的编码规则是这样的:
对于UTF-8单字节编码,该字节的最高位设为0,剩余未填入字符的Unicode编号,对于Unicode编号在0x00000000~0x0000007F的字符,UTF-8只需要一个字节,兼容ASCII码。对于N字节的编码,第一个字节最高位开始,前N为为1,第N+1位设为0,剩余字节最高位设置10,这N个字节的其余空位填充该字符的Unicode编码,高位补0。具体可见下表:
Unicode编码 | UTF-8编码 | |||
第1个字节 | 第2个字节 | 第3个字节 | 第4个字节 | |
0x00000000~0x0000007F | 0xxx xxxx | |||
0x00000080~0x000007FF | 110x xxxx | 10xx xxxx | ||
0x00000800~0x00000FFF | 1110 xxxx | 10xx xxxx | 10xx xxxx | |
0x00010000~0x0010FFff | 1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx |
解码时,看第一个字节:
BOM(Byte Order Mark),我们在Nodepad++中的Encoding选项中可以看到诸如Encoding in UTF-8,以及Encoding in UTF-8-BOM这样的选项。带不带标签不影响对字符的编解码,但是这两种还是有一定区别的。比如我们常用的emWin,在emWin中文支持中,它只支持不带标签的UTF-8编码,如果使用带标签的UTF-8-BOM,虽然不会报错,但是在控件中无法显示对应的中文。
ANSI编码是Windows中的一种称呼,像GBK、GB2312都是ANSI编码,在不同语言的操作系统中,ANSI表示的编码是不同的,比如中文、泰文、法文都有各自的编码方式,这些编码方式对ASCII编码的扩展就是ANSI。