Unicode UTF-8 字符编码

What is Unicode?
Unicode provides a unique number for every character,
no matter what the platform,
no matter what the program,
no matter what the language.

很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为字节。再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为计算机

开始计算机只在美国用。八位的字节一共可以组合出256(28次方)种不同的状态。他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作:

遇上0×10, 终端就换行;

遇上0×07, 终端就向人们嘟嘟叫;

遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。

他们看到这样很好,于是就把这些0×20以下的字节状态称为控制码。他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做ANSI”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。

 

后来,就像建造巴比伦塔一样,世界各地都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128 255这一页的字符集被称扩展字符集。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧!

 

等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA10xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符,而原来在127号以下的那些就叫半角字符了。中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312“GB2312 是对 ASCII 的中文扩展。 

但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把GB2312 没有用到的码位找出来老实不客气地用上。后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为GBK标准,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS“Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍:一个汉字算两个英文字符!一个汉字算两个英文字符……”

 

因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个汉字系统,专门用来处理汉字的显示、输入的问题,像是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么倚天汉字系统才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊!

正在这时,大天使加百列及时出现了——一个叫ISO(国际标准化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称UCS, 俗称 “unicode“

unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些半角字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于半角英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

 

这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从unicode开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的一个字符!同时,也都是统一的两个字节,请注意字符字节两个术语的不同,字节是一个8位的物理存贮单元,而字符则是一个文化相关的符号。在unicode中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。

unicode同样也不完美,这里就有两个的问题,一个是,如何才能区别unicodeascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍,这是难以接受的。  

unicode在很长一段时间内无法推广,直到互联网的出现,为解决unicode如何在网络上传输的问题,于是面向传输的众多 UTFUCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicodeuft-8并不是直接的对应,而是要过一些算法和规则来转换。

Unicode 是一个符号集(Universal Multiple-Octet Coded Character Set),它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

unicode刚出来的时候,三个字节在计算机上有三种解析结果,三个字节可以是一个字符的表示,也可以是两个,也可以是三个字符,所以这套编码没被推广,没人用,只是一套标准闲放在那里。 

更详细的解释是,一个字符对应且只对应一个二进制代码,反过来,一个二进制代码对应且只对应一个字符。如果我们有一个字符,那么通过Unicode字符集很容易找到该字符对应的二进制码,但是,反过来,可能会出现混乱,为什么呢?情境如下:如果我们假设Unicode字符集有两个字符,比如说(这里我没有查阅Unicode字符集,虚构了两个,只是打个比方,请各位看官不要较劲):1100 1111 1111 0001 1111 0101 对应字符,一共三个字节,1100 1111 1111 0001对应字符,一共两个字节,恰好是这个字符二进制码的前两个字节。如果我们存的是即三个字节的那个代码,即1100 1111 1111 0001 1111 0101,然后让计算机进行解码时,它会从左向右依次读取一个数码,当读到1100 1111 1111 0001时它可能就停止,让这个字符与其对应,并不是我们当初想存的这个字符,只是因为这两个字符对应的二进制码前两个字节是一样的,这就是一个问题。Unicode字符集中对字符的编码是长度不确定的,其中有的字符是两个字符,有的是三个字符,这给计算机进行解码带来了困难(就像上述描述的情境),所以我们想给每个字符对应的二进制码前加上一个标记,让计算机看到这个标记就知道它将要读取几个字节,这样就防止了上述描述的两个字符因为前两个字节编码相同而误将三个字节的字符当成两个字节的字符读取了这种错误,那么这个标记该怎么加呢?

 

直到互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 Unicode 的实现方式之一。所以utf8是对unicode字符集进行编码的一种编码方式。 

Unicode 字符集为每一个字符分配一个码位

UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。会将一个码位编码为 1 4 个字节:  

UTF-8 的编码规则很简单,只有二条: 

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。 

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后 面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符

号的 Unicode 码。 

U+ 0000 ~ U+ 007F: 0XXXXXXX

U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX

U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX

U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

 

例如「知」的码位是 30693,记作 U+77E530693 的十六进制为 0x77E5)。

根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

       7    7    E    5

    0111 0111 1110 0101    二进制的 77E5

————————–

    0111   011111   100101 二进制的 77E5

1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)

11100111 10011111 10100101 代入模版

   E   7    9   F    A   5

 

这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。反之亦然。

 

补充:

Little endian Big endian

UCS-2 格式可以存储 Unicode 码(码点不超过0xFFFF)。以汉字 为例,Unicode 码是77E5,需要用两个字节存储,一个字节是77,另一个字节是E5。存储的时候,77在前,E5在后,这就是 Big endian 方式;E5在前,77在后,这是 Little endian 方式。 

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

第一个字节在前,就是大头方式Big endian),第二个字节在前就是小头方式Little endian)。

那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?

 Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做零宽度非换行空格zero width no-break space),用FEFF表示。这正好是两个字节,而且FFFE1

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

参考:
https://en.wikipedia.org/wiki/Unicode#Mapping_and_encodings
http://www.unicode.org/standard/WhatIsUnicode.html
https://github.com/miloyip/json-tutorial/blob/master/tutorial04/tutorial04.md
https://wenku.baidu.com/view/cb9fe505cc17552707220865.html
https://www.zhihu.com/question/23374078
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
Spread the love