Unicode、UTF-8、ASCII等编码方式浅述

关于编码问题,虽然在开发中经常用到,但是对于各种编码方式的实现、相互之间的转换、在实际场景中的使用区别等方面并没有深刻的理解。今天针对Unicode、UTF-8、ASCII、ANSI、GB2312/GBK、ISO8859-1等编码方式做一些解释和说明。

一、ASCII

首先从最基础的ASCII码开始说起。计算机真正的内部实现上,任何字符都是0和1的组合,那么ASCII码是最早是上世纪60年代的时候由美国人发明的用来统一表示英文大小写字母以及常用字符的。其为一个字节的表示,最早只用了0-127,也就是一个字节的后7个bit。这完全满足了英文区域人民的使用,用起来有了统一标准,很嗨啊。这就是最早的统一ASCII标准。

二、扩展 ASCII 编码

但是,随着计算机的传播和普及,欧洲区域的非英语系的人民需要表示自己的语言,而128个字符是不够的,这时候就使用了最后一个高位bit,也就是0-255这256个字符的表示都用了。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。

至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。

具体各种语言之间编码关系的对应就不多谈了,但是中间都是有各自使用的编码方式和通用编码方式之间的转换。要不然各国之间的语言在互联网上通信是很麻烦的。所以知道能转换就好了,不多介绍。

但是这里提到了通用码表,也就是中间转换的通用码表,随着互联网发发展,没有通用方式怎么能行呢。各种各样的编码方式成了系统开发者的噩梦,因为他们想把软件卖到国外。于是,他们提出了一个“内码表”的概念,可以切换到相应语言的一个内码表,这样才能显示相应语言的字母。在这种情况下,如果使用多语种,那么就需要频繁的在内码表内进行切换。

三、Unicode

最终,美国人意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,出于这个目的,Unicode诞生了。

Unicode 当然是一本很厚的字典,记录着世界上所有字符对应的一个数字。具体是怎样的对应关系,又或者说是如何进行划分的,就不是我们考虑的问题了,我们只用知道 Unicode 给所有的字符指定了一个数字用来表示该字符。

对于 Unicode 有一些误解,它仅仅只是一个字符集,规定了符合对应的二进制代码,至于这个二进制代码如何存储则没有任何规定。它的想法很简单,就是为每个字符规定一个 用来表示该字符的数字,仅此而已。至于怎么存储,怎么表示,是各种编码方式关心的。而Unicode的作用是通用内码表。

简单介绍一下Unicode的实现:

1. 代码点:

Unicode标准的本意很简单:希望给世界上每一种文字系统的每一个字符,都分配一个唯一的整数,这些整数叫做代码点Code Points)。

2.代码空间

所有的代码点构成一个代码空间Code Space),根据Unicode定义,总共有1,114,112个代码点,编号从0x00x10FFFF。换句话说,如果每个代码点都能够代表一个有效字符的话,Unicode标准最多能够编码1,114,112,也就是大概110多万个字符。最新的Unicode标准(7.0)已经给超过11万个字符分配了代码点。

3.代码平面

Unicode标准把代码点分成了17代码平面Code Plane),编号为#0#16。每个代码平面包含65,536(2^16)个代码点(17*65,536=1,114,112)。其中,Plane#0叫做基本多语言平面(Basic Multilingual Plane,BMP),其余平面叫做补充平面(Supplementary Planes)。Unicode7.0只使用了17个平面中的6个,并且给这6个平面起了名字,具体的:

  • Plane#0 BMP(Basic Multilingual Plane)大部分常用的字符都坐落在这个平面内,比如ASCII字符,汉字等。
  • Plane#1 SMP(Supplementary Multilingual Plane)这个平面定义了一些古老的文字,不常用。
  • Plane#2 SIP(Supplementary Ideographic Plane)这个平面主要是一些BMP中没有包含汉字。
  • Plane#14 SSP(Supplementary Special-purpose Plane)这个平面定义了一些非图形字符。
  • Plane#15 SPUA-A(Supplementary Private Use Area A)
  • Plane#16 SPUA-B(Supplementary Private Use Area B)

BMP是最重要的一个代码平面,大部分常用的字符都定义在这个平面内,在BMP中定义的代码点包括:

  • ASCII 总共有128个字符,占据了BMP的前128个代码点
  • ISO-8859-1 共256个字符,占据了BMP的前256个代码点
  • CJK Unified Ideographs 上图的红色区域(占据BMP大约1/3)定义了两万多个汉字,其中前20,902个汉字是按照《康熙字典》里笔画顺序排列的
  • Surrogate Code Points 从0xD800到0xDBFF的1024个代码点是High-surrogate代码点,从0xDC00到0xDFFF的1024个代码点是Low-surrogate代码点。这2048个代码点并不是有效的字符代码点,它们是为UTF编码保留的。一个High-surrogate代码点和一个Low-surrogate代码点组成一个代理对(Surrogate Pair),可以在UTF-16里编码BMP之外的某个代码点(1024^2+65,536=1,114,112)。

通过上面的介绍,我们可以清晰的知道,Unicode只是一种码表,表示了所有的字符。但是,对于这些字符具体在计算机中如何存储,如何识别并没有解决。一个多字节表示的复杂字符,怎么能不被误解码成多个单字节的简单字符,不通语言之间如何映射等这些问题亟需解决。

四、UTF-8

这个时候UTF-8这种编码方式应运而生。当然,任何一种解决大事情的方案都不是一蹴而就的,产生的过程也是漫长的,具体过程就不表了,简单描述一下。

UTF-8其实不是最先产生的统一编码方式,UTF-16、UTF-32都在它之前产生。但是UTF-16固定使用两字节,UTF-32固定使用4字节,对于使用bit较少的字符来讲,是极大的浪费。而且大多数的字符都没有那么多字节的表示。这时候UTF-8就产生了。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。

五、各种编码方式之间的转换

1.ANSI:这是默认编码方式,针对英文是ASCII编码,针对中文是GB2312的简体中文编码。GBK是繁体中文编码,向下兼容GB2312。

2.UTF-16 LE&BE:UTF-16是用两个字节来表示字符,那么对应于Unicode中两个字节来讲,哪个在前,哪个在后呢。比如前面的严字来讲,Unicode两个字符是4E25,而用UTF-16来表示的话,如果是BE那么就是4E25,而如果是LE,那么就是254E。LE和BE分别表示Little endian 和 Big endian,就是小头在前还是大头在前的意思。

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

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

为了弄清楚UTF-16文件的大小头顺序,在UTF-16文件的开首,都会放置一个U+FEFF字符作为Byte Order Mark(UTF-16LE以FF FE代表,UTF-16BE以FE FF代表),以显示这个文字档案是以UTF-16编码,其中U+FEFF字符在UNICODE中代表的意义是ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。

3.ISO8859-1 通常叫做Latin-1

ISO8859-1 通常叫做Latin-1,属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母a的编码为0x61=97。 很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符(java字符占2个字节),使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

后记

至此,基本的编码方式的理解梳理清楚了,看清细节的感觉有时候还挺好的。虽说朦胧感是美的来源,有些时候还是需要看清一些才踏实。

注:有任何问题可以一起探讨,本文参考了多处博客以及编码的官方文档。

猜你喜欢

转载自blog.csdn.net/michaelgo/article/details/81147002