为何进行编码
众所周知计算机的世界只有0和1,所以为了让计算机能表示我们人类能够理解的符号,我们必须做一些转换工作,所以编码就是指将人类认识的符号转化为0和1的过程。
如何进行编码
这个问题笔者认为是真正理解编码的关键,网上查到这个问题的时候,就会出现很多常见的编码格式,如ASCII、ISO-8859-1、GBK、UTF-8和UTF-16等等,但这些是实现编码的具体方式,它们都有自己的“个性”,不利于我理解编码的本质,所以理解编码的最好方式是得搞懂编码的“共性”。
首先要理解三个名词:字库表,编码字符集和字符编码。字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围,通俗的理解就是字库表存储的是我们人类能理解的所有字符。编码字符集,即用一个编码值code point来表示一个字符在字库中的位置。字符编码,将编码字符集和实际存储数值之间的转换关系,就是编码字符集的实际存储方式。举个例子就好理解了:一般来说都会直接将code point的值作为编码后的值直接存储。例如在ASCII中A在表中排第65位,而编码后A的数值是0100 0001也即十进制的65的二进制转换结果,这也说明了ASCII中编码字符集和字符编码是相同的数值。所以像ASCII这样编码字符集和字符编码都相同的编码方式就会给我们编码是直接将字库表中的字符转化为字符编码的错觉。
**
读到这里可能有的小伙伴会有疑惑,为什么不直接把编码字符集存储就好,还要多一步转换为字符编码?在小范围的国家和地区中,字符编码确实就是编码字符集,这并没有什么问题,但是如果字库表覆盖的范围是整个世界的符号文字,这里以Unicode字库表为例子,排序最后的字符可能需要3-4个字节(取决字符在库中的位置),但是其实在一些地区他们不需要用到序号这么后的符号,如果直接用编码字符集存储的话,这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三到四倍)。所以需要改变一下实际的存储格式。
Unicode和UTF-8
理解完上面的概念,那么理解Unicode和UTF-8的关系就比较简单了,网上的有些资料在说明Unicode的时候会强调Unicode只是字符集,而UTF-8是其具体编码实现,这句话其实现在看来没错,但是如果没有上面的概念,笔者在理解的时候还是挺难的。Unicode就是上文中提到的编码字符集,而UTF-8就是字符编码,即Unicode的一种实际存储方式,UTF-16和UTF-32也是Unicode的实际存储方式之一。
**
UTF-8的编码方式
UTF-8的编码规则:
- 如果一个字节,最高位(第八位)为0,表示这是一个ASCII字符,表示这是一个ASCII字符(00~7F)。所以所有ASCII编码已经是UTF-8了。
- 如果一个字节以11开头,连续的1的个数暗示这个字符的字节数,例如110xxxxx代表它是双字节UTF-8字符的首字节。
- 如果一个字节以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。
下面以中文“严”为例子:
“严”的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此“严”的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。
UTF-16的编码方式:
UTF-16的编码规则:UTF-16是定长的表示方法,其用两个字节来表示Unicode码,不论什么字符都可以用两个字节表示,两个字节是16个bit,所以叫UTF-16。所以字母A的UTF-16表示为0041,相对UTF-8存储空间多了一倍。
参考文献:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://cenalulu.github.io/linux/character-encoding/
《深入分析JavaWeb技术内幕》 许令波著