基本常识
1.位和字节
位(bit)是指计算机里存放的二进制值(0/1),而8个位组合成的“位串”称为一个字节,容易算出,8个位的组合有256个组合方式,其取值范围是“00000000-11111111”,常用十六进制来表示。比如“01000001”就是一个字节,其对应的十六进制值为“0x41”。
而我们通常所讲的字符编码,就是指定义一套规则,将真实世界里的字母/字符与计算机的二进制序列进行相互转化。如我们可以针对上面的字节定义如下的转换规则:01000001(0x41)<-> 65 <-> A
即用字位序01000001
来表示字母A
。
2.拉丁字符
拉丁字符是当今世界使用最广泛的符号了。通常我们说的拉丁字母,指的的是基础拉丁字母,即指常见的”ABCD“等26个英文字母,这些字母与英语中一些常见的符号(如数字,标点符号)称为基础拉丁字符,这些基础拉丁字符在使用英语的国家广为流行,当然在中国,也被用来当作汉语拼音使用。在欧洲其它一些非英语国家,为满足其语言需要,在基础拉丁字符的基础上,加上一些连字符,变音字符(如Á
),形成了派生拉丁字母,其表示的字符范围在各种语言有所不同,而完整意义上的拉丁字符是指这些变体字符与基础拉丁字符的全集。是比基础拉丁字符集大很多的一个集合。
编码标准
1.拉丁编码
ASCII的全称是American Standard Code for Information Interchange(美国信息交换标准代码)。顾名思义,这是现代计算机的发明国美国人设计的标准,而美国是一个英语国家,他们设定的ASCII编码也只支持基础拉丁字符。ASCII的设计也很简单,用一个字节(8个位)来表示一个字符,并保证最高位的取值永远为’0’。即表示字符含义的位数为7位,不难算出其可表达字符数为27 =128个。这128个字符包括95个可打印的字符(涵盖了26个英文字母的大小写以及英文标点符号能)与33个控制字符(不可打印字符)。例如下表,就是几个简单的规则对应:
字符类型 | 字符 | 二进制 | 16进制 | 10进制 |
---|---|---|---|---|
可打印字符 | A |
01000001 |
0x41 |
65 |
可打印字符 | a |
01100001 |
0x61 |
97 |
控制字符 | \r |
00001101 |
0x0D |
13 |
控制字符 | \n |
00001010 |
0xA |
10 |
ASCII是美国人设计的,只能支持基础拉丁字符,而当计算机发展到欧洲,欧洲其它不只是用的基础拉丁字符的国家(即用更大的派生拉丁字符集)该怎么办呢?
当然,最简单的办法就是将美国人没有用到的第8位也用上就好了,这样能表达的字符个数就达到了28 =256个,相比较原来,增长了一倍, 这个编码规则也常被称为EASCII
。EASCII
基本解决了整个西欧的字符编码问题。但是对于欧洲其它地方如北欧,东欧地区,256个字符还是不够用,如是出现了ISO 8859
,为解决256个字符不够用的问题,ISO8859
采取的不再是单个独立的编码规则,而是由一系列的字符集(共15个)所组成,分别称为ISO8859-n
(n=1,2,3…11,13…16,没有12)。其每个字符集对应不同的语言,如ISO 8859-1
对应西欧语言,ISO 8859-2
对应中欧语言等。其中大家所熟悉的Latin-1
就是ISO8859-1
的别名,它表示整个西欧的字符集范围。需要注意的一点的是,ISO8859-n
与ASCII
是兼容的,即其0000000(0x00)-01111111(0x7f)
范围段与ASCII保持一致,而10000000(0x80)-11111111(0xFF)
范围段被扩展用到不同的字符集。
2.中文编码
以上我们接触到的拉丁编码,都是单字节编码,即用一个字节来对应一个字符。但这一规则对于其它字符集更大的语言来说,并不适应,比如中文,而是出现了用多个字节表示一个字符的编码规则。常见的中文GB2312
(国家简体中文字符集)就是用两个字节来表示一个汉字(注意是表示一个汉字,对于拉丁字母,GB2312
还是是用一个字节来表示以兼容ASCII
)。我们用下表来说明各中文编码之间的规则和兼容性。
GB2312 | BIG5 | GBK | GB18030 | |
---|---|---|---|---|
作用 | 国家简体中文字符集 | 统一繁体字符集 | GB2312的扩展,加入对繁体字的支持 | 中日韩文字编码 |
字节数 | 变字节: 1字节-兼容ASCII 2字节-汉字 |
2字节 | 2字节 | 变字节: 1字节-兼容ASCII 2字节,4字节 |
范围 | 7445字符,6763汉字 | 21886字符 | 21886字符 | 27484字符 |
兼容性 | 兼容ASCII | 兼容ASCII但和GB2312冲突 | 兼容GB2312 | 兼容GB2312 |
对于中文编码,其规则实现上是很简单的,一般都是简单的字符查表即可,重要的是要注意其相互之间的兼容性问题。如如果选择BIG5字符集编码,就不能很好的兼容GB2312,当做繁转简时有可能导致个别字的冲突与不一致,但是GBK与GB2312之间就不存在这样的问题。
3.Unicode
针对不同的语言采用不同的编码,有可能导致冲突与不兼容性,如果我们打开一份字节序文件,如果不知道其编码规则,就无法正确解析其语义,这也是产生乱码的根本原因。有没有一种规则是全世界字符统一的呢?当然有,Unicode
就是一种。为了能独立表示世界上所有的字符,Unicode
采用4个字节表示一个字符,这样理论上Unicode
能表示的字符数就达到了2^31= 2147483648 = 21 亿左右个字符,完全可以涵盖世界上一切语言所用的符号。我们以汉字”微信“两字举例说明:
微 <->
\u5fae
<->00000000 00000000 01011111 10101110
信 <->\u4fe1
<->00000000 00000000 01001111 11100001
容易从上面的例子里看出,Unicode对所有的字符编码均需要四个字节,而这对于拉丁字母或汉字来说是浪费的,其前面三个或两个字节均是0,这对信息存储来说是极大的浪费。另外一个问题就是,如何区分Unicode与其它编码这也是一个问题,比如计算机怎么知道四个字节表示一个Unicode中的字符,还是分别表示四个ASCII的字符呢?
以上两个问题,困扰着Unicode
,让Unicode
的推广上一直面临着困难。直至UTF-8
作为Unicode
的一种实现后,部分问题得到解决,才得以完成推广使用。说到此,我们可以回答文章一开始提出的问题了,UTF-8
是Unicode
的一种实现方式,而Unicode
是一个统一标准规范,Unicode
的实现方式除了UTF-8
还有其它的,比如UTF-16
等。
话说当初大牛Ben Thomson吃饭时,在一张餐巾纸上,设计出了UTF-8
,然后回到房间,实现了第一版的UTF-8
。关于UTF-8
的基本规则,其实简单来说就两条(来自阮一峰老师的总结):
规则1:对于单字节字符,字节的第一位为0
,后7位为这个符号的Unicode
码,所以对于拉丁字母,UTF-8
与ASCII
码是一致的。
规则2:对于n
字节(n>1
)的字符,第一个字节前n
位都设为1
,第n+1
位为0
,后面字节的前两位一律设为10
,剩下没有提及的位,全部为这个符号的Unicode
编码。
通过,根据以上规则,可以建立一个Unicode取值范围与UTF-8字节序表示的对应关系,如下表:
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是\u5fae
,二进制表示是00000000 00000000 01011111 10101110
,其取值就位于0000 0800-0000 FFFF
之间,所以其UTF-8
编码为11100101 10111110 10101110
。
通过以上简单规则,UTF-8
采取变字节的方式,解决了我们前文提到的关于Unicode
的两大问题。同时,作为中文使用者需要注意的一点是Unicode(UTF-8)
与GBK
,GB2312
这些汉字编码规则是完全不兼容的,也就是说这两者之间不能通过任何算法来进行转换,如需转换,一般通过GBK
查表的方式来进行。