在计算机中,所有信息最终都是用二进制来表示的。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,从00000000
到11111111
。
关于byte还有个单位换算:1024byte表示1KB,1024KB表示1MB,1024MB表示1GB,1024GB表示1TB,1024TB表示1PB。
计算机于1946年2月15日诞生于美国的宾夕法尼亚大学,起名叫做ENIAC,这部计算机体积非常的庞大,占满一整个房间,耗电量也非常高,当有人使用它的时候,全镇的人都会知道,因为他们家的电灯都会变暗。
ASCII 码
当时为了在计算机中表示英文中的字符,美国制定了一套字符编码,对英文字符与二进制位之间做了映射关系,这被称为 ASCII 码:
其中的编号从0~32的字符,被赋予特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作:
遇上0x10, 终端就换行
遇上0x07, 终端就向人们嘟嘟叫
遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。
这些字符就称为”控制码”。
GB2312
拥有了ASCII码之后,英文就可以在计算机当中完美的表示出来了,但随着计算机的普及,其他的国家也要求将自己国家的语言编入计算机当中,他们纷纷在基于ASCII码之上,编写了自己的码表,比如中国的GB2312。
GB2312规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
Unicode
因为各个国家都编写了自己的码表,导致同一个二进制数字可以被解释成不同的符号,对信息的交流带来了不变,特别是邮件传输的时候,经常会出现乱码。
为了解决这个问题,ISO(国际标谁化组织)决定着手解决这个问题,他们呼吁各个国家不要在编写新的码表,然后他们制作了Unicode,全称是:Universal Multiple-Octet Coded Character Set,全球统一字符编码集。
注意Unicode只是单纯的规定了数字和字符之间的关系,比如规定了20064表示汉字:习,但是Unicode并没有规定改如何在计算机中存储信息。
UTF-8
UTF表示Unicode TransferFormat,UTF-8是一种 Unicode 的实现方式,也是在互联网上使用最广的实现方式,其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为
0
,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于
n
字节的符号(n > 1)
,第一个字节的前n
位都设为1
,第n + 1
位设为0
,后面字节的前两位一律设为10
。剩下的没有提及的二进制位,全部为这个符号的Unicode 码。
下表总结了编码规则,字母x表示可用编码的位:
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
跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0
,则这个字节单独就是一个字符;如果第一位是1
,则连续有多少个1
,就表示当前字符占用多少个字节。
下面,还是以汉字严为例,演示如何实现 UTF-8 编码。
严
的 Unicode 是4E25(100111000100101)
,根据上表,可以发现4E25
处在第三行的范围内(0000 0800 - 0000 FFFF)
,因此严
的UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx
,然后,从严
的最后一个二进制位开始,依次从后向前填入格式中的x
,多出的位补0
,这样就得到了,严
的 UTF-8 编码是11100100 10111000 10100101
,转换成十六进制就是E4B8A5
。
UTF-8的缺点
1、对于欧美地区一些以英语为母语的国家 UTF-8 简直是太棒了,因为它和 ASCII 一样,一个字符只占一个字节,没有任何额外的存储负担;但是对于中日韩等国家来说,UTF-8 实在是太冗余,一个字符竟然要占用 3 个字节,存储和传输的效率不但没有提升,反而下降了。所以欧美人民常常毫不犹豫的采用UTF-8,而我们却需要要犹豫一下。
2、变长字节表示带来的效率问题,因为UTF-8是变长字节表示,因此无论是计算字符数,还是执行索引操作效率都不高。为了解决这个问题,常常会考虑把 UTF-8 先转换为 UTF-16 或者 UTF-32 后再操作,操作完毕后再转换回去。而这显然是一种性能负担。
UTF-16
UTF-16使用两个或者四个字节来存储所有的字符,一开始是使用两个,但是我们知道,两个字节最多只能够存储65536(0x0000~0xFFFF)个字符,所以后来改版,对其进行了扩展,规定,小于65536(0xFFFF)的用两个字节表示,否则就用四个字节表示。
UTF-16很明显的一个问题是,对于像ASCII中的可以使用一个字节来存放的字符,也使用两个或者四个字节来存放,空间浪费很大,UTF-16的另外一个问题是存在大端小端的问题。
大端小端
以汉字严为例,Unicode 码是4E25
,需要用两个字节存储,一个字节是4E
,另一个字节是25
。存储的时候,4E
在前,25
在后,这就是Big endian 方式;25
在前,4E
在后,这是Little endian 方式。
这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是“小头方式”(Little endian)。
那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做“零宽度非换行空格”(zero width no-break space),用FEFF
表示。这正好是两个字节,而且FF
比FE
大1
。
如果一个文本文件的头两个字节是FE FF
,就表示该文件采用大头方式;如果头两个字节是FF FE
,就表示该文件采用小头方式。