计算机只能处理二进制的数据,其它形式的数据都只能转换为二进制后才能被cpu处理及存储。转换就涉及到要有一套字符编码,即字符与二进制的对应关系。
1. ASCII 编码
全称American Standard Code for Information Interchange 美国信息交换标准代码,上个世纪60年代,美国制定的一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。ASCII用1个字节(8位二进制)来表示一个字符,因此八个二进制位就可以组合出256种状态(从0000000到11111111),但ASCII码一共规定了128个字符的编码,只占用了一个字节的后面7位,最前面的1位统一规定为0。比如:字母a,用二进制表示01100001(十进制值为97)。
英语128个符号编码已足够,但是显示不同国家的语言时,128个符号是不够的。于是有的国家开始利用ASCII编码里的高位,0—127表示的符号是一样的,不同国家在128—255这一段所表示的字符却不同。在字符比较多的国家,255个字符也是不够的,因此仍采用1个字节的编码受到限制。
其它国家为了显示本国的语言,都对ASCII码进行了扩展,加入了本国的语言编码,这种编码方式为ANSI编码,用2个字节来表示,ANSI编码跟操作系统有直接关系,你安装什么操作系统,那你的ANSI编码就是相应的编码。例如:我们安装的是中文操作系统,对应的默认编码GB2312。
- GB2312编码:简体中文、全角字符编码,理论上最多表示65536个汉字;
- GBK编码:对GB2312进行了扩展,用于显示罕见汉字;
- BIG5编码:繁体汉字编码;
- JIS编码:日本文字编码。
2. Unicode
在打开一个文本文件之前,需要知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。这是因为,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。
若有一种编码,将世界上所有的符号都纳入其中,每一个符号都给予一个独一无二的编码,就可以解决乱码问题,Unicode应运而生。
Unicode编码集合可以容纳100多万个符号,每个符号的编码都不一样。比如U+0042表示英语的大写字母"B",U+1F600表示"😀",U+8DF3表示汉字"跳"。具体的符号对应表,可以查询unicode.org、Unicode Utilities或者专门的汉字对应表。
Unicode编码的符号集只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如汉字"跳"的unicode是十六进制数8DF3,转换成二进制数足足有15位(1000110111110011),也就是说这个符号的表示至少需要2个字节。也会有序号更大的符号,可能需要3、4个字节,甚至更多。
那么,如何才能区别Unicode和ASCII?Unicode编码怎么在计算机内存储呢?若每个符号都占用四个字节编码表示,那么每个英文字母前三个字节是0,极大的浪费存储空间,原英文文本文件的大小会因此大出三倍,显然无法接受。
3. UTF-8
UTF-8是Unicode的实现方式之一,这种Unicode/UTF-8统一编码方式有利于数据的传输和计算机科学的发展。其他实现方式还包括UTF-16(字符用两个字节或四个字节编码)和UTF-32(字符用四个字节编码),不过在互联网上基本不用。
UTF-8的编码规则:
- 对于单字节的字符,字节的第一位设为0,后面7位为这个符号的Unicode,因此对于英语字符,UTF-8和ASCII码相同;
- 对于n字节的字符(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
Unicode符号范围 | UTF-8编码方式
(十六进制)|(二进制)
--------------------+---------------------------------------------
0000 0000 — 0000 007F | 0xxxxxxx
0000 0080 — 0000 07FF | 110xxxxx 10xxxxxx
0000 0800 — 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 — 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx已知"跳"的Unicode是4DF3(1000110111110011),根据其值位于的范围可知,"跳"的UTF-8编码是"11101000 10110111 10110011",转换成十六进制就是E8B7B3。
也就是说,UTF-8编码是一种变长的编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
在Mac上创建一个txt文档,在偏好设置中,能够选择当前文档的存储编码方式,也可进行编码之间的转换:
4. 编码中的Little endian和Big endian
若将一个十六进制数据"8DF3"直接进行存储,需要用两个字节存储,一个字节是8D,另一个字节是F3。那么,计算机在存储空间的地址顺序上,是将8D在前,还是将F3在前呢?
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大端序;如果头两个字节是FF FE,就表示该文件采用小端序。
新建一个记事本文本文件,输入一个汉字作为内容,依次采用ANSI、Unicode、Unicode big endian 和 UTF-8编码方式保存。然后,可以通过"十六进制"的形式观察该文件的内部编码方式、存储有何不同。
1)ANSI:文件的编码就是两个字节"D1 CF",这正是"严"的GB2312编码,这也暗示GB2312是采用大头方式存储的。
2)Unicode:编码是四个字节"FF FE 25 4E",其中"FF FE"表明是小头方式存储,真正的编码是4E25。
3)Unicode big endian:编码是四个字节"FE FF 4E 25",其中"FE FF"表明是大头方式存储。
4)UTF-8:编码是六个字节"EF BB BF E4 B8 A5",前三个字节"EF BB BF"表示这是UTF-8编码,后三个"E4B8A5"就是"严"的具体编码,它的存储顺序与编码顺序是一致的。
5. 总结
在计算机内存中统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8(或其他)编码。
不同的字符集中,字符有不同的字符编码,所以要想判断某个输入的数据是不是某类字符的时候,先确定当前使用的字符集,找到该字符集下该类型字符对应的字符编码范围,即可判断,如判断GB2312下的汉字。
系统只是把这个字符告诉终端,终端去字体中找到这个字符对应的图像,再把这个图像显示出来。如果字体中没有,那就会显示方框等所谓的乱码。
emoji的支持也是一样的,所以实际上是字体的功劳。可能系统在emoji的渲染上支持一些额外的特性(比如颜色,或者干脆用图片来代表emoji字符)。
现代操作系统内部都是用Unicode来处理字符的。设定字符集实际上是告诉系统如何处理外码和内码的对应,比如同一个字符,在UTF-8和UTF-16中的编码可能是不同的,字节数量也可能是不同的,但是对应的unicode其实同一个。比如系统处理一个字符,根据设定的字符集找到对应的UTF-8编码输出给终端,但是终端却是用UTF-16的规则来理解这些编码,那自然就会出错了。
参考文章,感谢:
https://www.cnblogs.com/ooon/p/4818574.html
https://www.cnblogs.com/cthon/p/9297232.html
https://blog.csdn.net/LightUpHeaven/article/details/92001322