乱码的缘由-字符集编码

283 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中 (如'a'用什么表示) 称为编码;反之,将存储在计算机中的二进制数解析显示出来,称为解码,如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则将导致'a'解析成'b'或者乱码。

一、基础概念

1. 字节 (byte)

字节 (byte) 是计算存储容量的一种计量单位。规定1字节 (byte) =8位 (bit) 。例如 :00001111 这个8位的二进制数就占了一个字节的存储容量。

计算机是通过0和1组成的二进制位来表达万物的。其中,每1位 (bit) 中只能存储0或者1,这样多个位就形成了某个特定的含义。

2. 字符 (Character)

字符 (Character) 是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。如我国有汉字符号,汉字分为简体和繁体;俄罗斯有俄语符号;日本有日语符号...这些符号都属于字符。

3. 字符集

字符集有广义和狭义之分。

  • 广义的字符集是一个关于字符规则的集合,包括:字库表 (character repertoire) 、编码字符集 (coded character set) 、字符编码 (character encoding form) 。
  • 狭义的字符集就是指的编码字符集,如无说明,下面所说的字符集都默认为广义字符集。

字库表,是一个存储该字符集中的所有字符 (包括控制字符、可显示字符等) 的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
编码字符集,即用一段连续的二进制地址来对应字库表中的字符,这个二进制地址也叫做编码值 (code point) ,而这个值也称为某一个字符在编码字符集中的序号。编码字符集有很多种,每编码字符集中所包含的字符个数也各不不同,常见编码字符集有:Unicode、ASCII、GB2312、GBK、 GB18030等。

  • Unicode:也叫统一编码字符集,它包含了几乎世界上所有的已经发现且需要使用的字符 (如中文、日文、英文、德文等) 。
  • ASCII:早期的计算机系统只能处理英文,所以ASCII也就成为了计算机的缺省编码字符集,包含了英文所需要的所有字符。
  • GB2312:中文编码字符集,包含ASCII字符集。ASCII部分用单字节表示,剩余部分用双字节表示。
  • GBK:GB2312的扩展,完整包含了GB2312的所有内容。
  • GB18030:GBK字符集的超集,常叫大汉字字符集,也叫CJK (Chinese,Japanese,Korea) 字符集,包含了中、日、韩三国语言中的所有字符。
    字符编码,是定义在编码字符集上的一种规则,这种规则定义了编码字符集中的编码值 (code point) 和实际存储值之间的转换关系。而字符,恰好就是根据字符编码方案转换为一个二进制数值存储在计算机中。

二、编码字符集和字符编码实例

1.ASCII

ASCII既是编码字符集,又是字符编码。也就是说ASCII直接将字符在编码字符集中的序号作为字符在计算机中的存储值。如:在ASCII中A排第65位,序号是0100 0001 (65) ,编码后A的存储值也是0100 0001 (65) 。

2.Unicode

Unicode编码字符集的字符编码有UTF-8、UTF-16、UTF-32等多种字符编码,常用的是UTF-8。
UTF-8编码为变长编码。UTF-8不是编码规范,而是编码方式。UTF-8的最小编码单位 (code unit) 为一个字节。

一个字节的前1-3个bit为描述性部分,后面为实际序号部分。编码规则如下:

  • 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分 (7个bit) 代表在Unicode中的序号。如:0100 0001我们就是用0100 0001来表示,对于一个字节的字符,其实就是直接使用地址表示。
  • 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间,且第二个字节以10开头。110之后的所有部分 (5个bit) 加上后一个字节的除10外的部分 (6个bit) 代表在Unicode中的序号。
  • 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间,且第二、第三个字节以10开头。1110之后的所有部分 (4个bit) 加上后两个字节的除10外的部分 (12个bit) 代表在Unicode中的序号。
  • 如果一个字节以11110开头,那么代表当前字符为四字节字符,占用4个字节的空间。且第二、第三、第四个字节以10开头。11110之后的所有部分 (3个bit) 加上后两个字节的除10外的部分 (18个bit) 代表在Unicode中的序号。
Unicode中的字符序号bit数编码后的UTF-8值byte数
0000 0000 ~0000 007F0~70XXX XXXX1
0000 0080 ~0000 07FF8~11110X XXXX 10XX XXXX2
0000 0800 ~0000 FFFF12~161110 XXXX 10XX XXXX 10XX XXXX3
0001 0000 ~001F FFFF17~211111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX4

根据UTF-8编码规则可知,英文在UTF-8字符编码后只占1个字节,中文占了3个字节。

Ps:Unicode转换为UTF-8需要的字节数可以根据这个规则计算:如果Unicode小于0x80 (Ascii字符) ,则转换后为1个字节。否则转换后的字节数为Unicode二进制位数+3再除以5。

Ps:Unicode转换为UTF-8时,可以将Unicode二进制从低位往高位取出二进制数字,每次取6位,并按格式填补。

3.编码转换

以'胡'字为例,胡在各种字符编码下十六进制存储值:

编码
ANSI编码BA FA
Unicode big endian编码FE FF 80 E1
Unicode编码FF FE E1 80
UTF-8编码E8 83 A1

①ANSI字符编码是一种默认字符编码,对于英文字符默认使用ASCII编码,对于简体中文文件默认使用GB2312编码。
②Unicode big endian 和 Unicode 编码都是以2个字节为一组进行存储,但 Unicode 编码将低位字节放前面,而Unicode big endian将高位字节放后面。UTF-8是根据Unicode big endian的存储值来进行编码转换的。

Unicode big endian编码用后面两个字节存储汉字,其与UTF-8编码转换过程如下:

胡的unicode big endian编码值 (80E1) :1000 000011 100001
胡的UTF-8编码值 (E8 83 A1) :11101000 10000011 10100001