一、前言
日常开发中经常会碰到字符串的展示和转换会出现乱码的问题,特别时碰到中文的处理,然后就是网上找各种解决方案,ctrl+c/ctrl+v各种代码到IDE上编译运行,看能不能正常显示。结果就是,在开发环境中调试好的代码,一部署到生产环境上,乱码的问题依然存在。问题说难,其实也不难,只要弄清楚计算机中的字符编码和解码原理,问题即可迎刃而解。下面就对这段时间的学习做个梳理。
二、为什么需要编码与解码
我们知道计算机中的各种数据存放形式都是以二进制方式存放,字符也不例外,虽然它展现给我们时某个特定的图形(点阵),但在计算机中它是以某个数值进行存放。根据计算机学习的定义,我们把这样的二进制数值序列称作字节,这时就有了某个字符需要与某个字节对应的关系,即字符关系表,后面会具体介绍这个关系表到底是什么。于是就有了编码和解码的过程,即:
编码:将字符转换为对应的二进制序列的过程;
解码:将二进制序列转换为对应的字符的过程。
三、字符与二进制数值序列的对应关系表
在上面我们谈到展现给我们的字符实际在计算机中是一个对应的二进制序列值,那这个对应关系表又是怎样的呢?下面从字符编码的发展过程做个介绍。
(一)开天辟地的初创期:ASCII表
因为计算机最先在美国诞生,他们是最先定义字符和二进制序列值对应表的,于是就产生ASCII码,其实全称是美国标准信息交换代码American Standard Code for Information Interface。它是基于拉丁字母的一套编码,主要用于向用户显示现代英语和其他西欧语言字母。它被设计为用一个字节来表示一个字符,因为一个字节有8位二进制位,故其可以表示2^8=256个字符,实际上有用的由128字符,还有128字符是预留后续扩展的。
(二)各自为政的混乱期:各国自建的编码表
随着计算机科技的不断发展,越来越多的国家开始使用计算机。但是原先的ASCII表只能表示英文字符,对于热爱自己的国民显得友好程度不好,学习成本也大,不利于计算机的普及。能不能把自己国家的语言符号也放到计算机中表示呢?显然是可以的,但原先ASCII只能表示256个字符,对于自己国家成千上万的语言符号没办法支持,如汉字汉字的数量将近十万个,就日常所使用的汉字也有几千字。于是人们就开始制定自己的字符关系对应表,就有了我们中国的字符关系对应表GB2312、GBK、GB18030(具体见:彻底搞明白 GB2312、GBK 和 GB18030 - 知乎)。
1.GB2312。1980 年,中国发布了第一个汉字编码标准,也即 GB2312 ,全称 《信息交换用汉字编码字符集·基本集》,通常简称 GB (“国标”汉语拼音首字母), 共收录了 6763 个常用的汉字和字符,此标准于次年5月实施,它满足了日常 99% 汉字的使用需求。
GB2312 把每个汉字都编码成两个字节,第一个字节是高位字节,第二个字节是低位字节.
比如:"中" 字的区位码是 54 48,对应的十六进制是0x36 0x30,因此它的内码为 (0x36 + 0xA0) (0x30 + 0xA0),也即 0xD6 0xD0
2.GBK。由于有些汉字是在 GB2312 标准发布之后才简化的,还有一些人名、繁体字、日语和朝鲜语中的汉字也没有包括在内,所以,在 GB2312 的基础上添加了这部分字符,就形成了 GBK ,全称 《汉字内码扩展规范》,共收录了两万多个汉字和字符,它完全兼容 GB2312。GBK 于 1995 年发布,不过它只是 "技术规范指导性文件",并不属于国家标准。
3.GB18030。GB18030 全称《信息技术 中文编码字符集》 ,共收录七万多个汉字和字符, 它在 GBK 的基础上增加了中日韩语中的汉字 和 少数名族的文字及字符,完全兼容 GB2312,基本兼容 GBK。
(三)兼容并包的统一期:Unicode编码表
因为混乱期各国制定了自有的编码系统,国家之家无法正常交流,于是有人就设计了一套世界统一的字符编码来存放所有国家所使用的文字符号,即Unicode。Unicode的学名是(Universal Multiple-Octet Coded Character Set),简称为UCS。Unicode由Unicode联盟开发的一种字符编码方案,旨在通过对所有人类语言中的字符进行编码,实现跨平台、跨应用程序和跨语言的数据交换。Unicode又被称为 统一码、万国码、单一码。Unicode规定所有的字符和符号最少由2个字节(16位)来表示,所以Unicode码可以表示的最少字符个数为2^16=65536。
标准的 Unicode采用4个字节表示一个字符串。比如,U+0639 表示阿拉伯字母 Ain,U+0041 表示英语的大写字母 A,U+4E6D 表示汉字"乭 "。显然,Unicode 和 GBK 是不兼容的。
(四)勤俭持家的优化期:UTF-8编码
统一期产生的Unicode编码表使用4个字节来表示一个字符,对于只需要1个字节就可以表示文字符号的国家来说,显然很不公平,而且当时的存储设备价格昂贵,没那么多钱买来存放电子数据。于是人们开始对Unicode编码进行了优化,设计出了UTF-8编码。UTF-8不再要求最少要使用2个字节来编码,而是通过编码规则将所有字符进行了分类,如ASCII用1个字节表示,中文用2-3个字节表示。且Unicode 只是规定了字符如何编码,并没有规定如何存储和传输。传输中二进制序列和编码的二进制不一样,编码采用unicode,储存传输采用UTF-8,下文会具体介绍。
跟网络通讯中的如何区分连续的二进制位一样,UTF-8这种不同长度的编码方式,对于连续的字节序列计算机是如何区分到底1个字节表示1个字符还是2个字节表示1个字符。于是人们规定:
如果一个字节的第一位为0,表示这个字节表示1个字符,如ASCII码。
如果第一个字节的前 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 |
可以这样理解:如果第一位是 1,那么看他后面连续有多少个 1,就表示这个字符占用了多少个字节。后面连续的字节中前2位为10,表示都是用于表示这个字符的字节码。
例如,汉字“我”的Unicode码是0x6211,二进制 110001000010001,对应第三行(0000 0800~0000 FFFF),因此"我"需要三个字节,格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"我"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了"我"的UTF-8编码是"11100110 10001000 10010001 ",转换成十六进制就是E68891,这才是最终存储在计算机中的二进制编码。
注意:网络上有很多在线 UTF-8 编码转换工具,说是可以把汉字转换成 UTF-8 编码,其实大多数工具只是把汉字转换成了与之对应的 Unicode 码点,并不是真正在存储和传输过程中的 UTF-8 编码。可以查汉字对应的 UTF-8 编码和 Unicode 编码,可以看出这两者是不同的。
除了 UTF-8 之外,Unicode 的实现方式还有 UTF-16 ,UTF-32 。 UTF-16 使用2~4个字节表示一个字符,UTF-32 则使用标准的4个字节表示一个字符,与其Unicode码一一对应。无论采用哪种表现形式,同一字符所对应的 Unicode 码都是一样的,只不过在存储和传输的时候,把码做了不同的转换。