Unicode编码详解

今天看到一个面试问题:Unicode 和 UTF-8 有何区别?

我的简单理解就是Unicode 是字符集,而UTF-8是编码集/方式,之前在HTML字符集里总结过相关问题。但是今天搜索后又有一些更深的理解,这里记录下

把这些基础写了出来,是觉得就算是基础有些还是不会,所以总结下。基础很重要,基础越熟练,你走的越快。

【引文】

广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码规则。

简单来说:

Unicode 是「字符集」,符号范围是16进制

UTF-8 是「编码规则」,范围是二进制

其中:

字符集:为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)

编码规则:将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)

也就是说unicode是字符集,而utf8是unicode的一种编码方式

【主体】

我今天用了一个白天的时间把这个问题好好梳理了一遍,来这里做分享。

一.一切的开始-----ASCII

      计算机是无法识别字母和符号的,他只能用数字来做处理,那么此时ASCII码孕育而生,所谓ASCII码,就是将英文字母和常用符号用特定的数字去表达。比如:字母A用ASCII编码是十进制的65,二进制的01000001;字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;

      我们可以这样理解ASCII码,她就是一个字典,是英文世界和计算机世界沟通的通道。这个通道让一开始的沟通变顺畅。就这样计算机和程序员愉快的交流着。可是随着发展问题随之而来。中文和其他世界的也要和计算机沟通,中文和其他的语言的的进入,那ASCII肯定是不够的。

      我们知道,一个字母和符号是一个字节,而一个汉字是两个字节。所以在中文输入的时候,ASCII是不能用的。一时间,编码的世界就像是进入了战国,每个地方都在制定自己编码规则。中国制定了GB2312编码,用来把中文编进去。日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里。编码的世界混乱了。

二.分久必合

      计算机的实质是交流,和任何人交。所以这个分乱规则肯定是不可以的。大家一商量,把所有的字符写在一个规则下,这样Unicode出生了。Unicode包含我们现在可以用到的所有字符,Unicode也就出一个存下来所有符号的字典,重新担当起计算机世界和人类世界的翻译工作。

      到这里我再说一句,对于理解Unicode你将它要理解为字典。我们可以随时把他当做的翻译的依据。但是对于Unicode我们也是编码的。其中一开始编码方式是UCS-2。一般的汉字是两个字节,所以大多的UCS-2用两个字节来表示,但是某些偏僻的汉字和符号用4个字节。可是表示的多也会产生问题。就是我用一个英文字符的时候,我只需要用一个字节就可以存储,那么用Unicode这个字典进行转化的时候。我们知道计算机的内存在以前是很稀缺的资源,所以这种浪费资源的事我们是不会干的。

三.UTF-8由来

      我们为了让内存占用更加少,开始用另一种编码方式,基于Unicode让英文字符和符号占有1个字节。此时的汉子是占3~6个字节。所以在英文文档中,用UTF-8是节省空间的。还有一点值得注意,就是UTF-8和ASCII的编码在英文和一些字符的表达上是一样的。所以带来的好处就是,在一些使用ASCII编码的旧的系统和设备上,用UTF-8是可以继续使用的。

      所以会有这样的答复:UTF-8是对Unicode编码的一种编码方式

      由来原因:节省流量和硬盘

但是:一个汉字在UTF-8编码的情况下可能需要三个字节,使用gbk两个字节就足够了,所以有时候为了节省流量和硬盘,在面对中文世界时,也有使用gbk或者gbk2312编码的

     关于进制的介绍,我在文章关于进制里做了总结

 【END】

 最后分享下别人的答复:

 ①UTF-8是对Unicode编码的一种编码方式。

原因:节省流量和硬盘

但是:一个汉字在UTF-8编码的情况下可能需要三个字节,使用gbk两个字节就足够了,所以有时候为了节省流量和硬盘,在面对中文世界时,也有使用gbk或者gbk2312编码的

 ②Unicode就是包含所有字符的字符集,解决了世界各地每个地方所用的编码方式不一样的尴尬,让我们更好沟通。UTF-8就是为了解决Unicode存在内存问题,将Unicode再次编码的一种编码方式

 ③可以这样来理解:

      字符串是由字符构成,字符在计算机硬件中通过二进制形式存储,这种二进制形式就是编码。如果直接使用 “字符串↔️字符↔️二进制表示(编码)” ,会增加不同类型编码之间转换的复杂性。

所以引入了一个抽象层,“字符串↔️字符↔️与存储无关的表示↔️二进制表示(编码)” ,这样,可以用一种与存储无关的形式表示字符,不同的编码之间转换时可以先转换到这个抽象层,然后再转换为其他编码形式。在这里,unicode 就是 “与存储无关的表示”,utf—8 就是 “二进制表示”。

 ④计算机内只能保存101010等二进制数据,那么页面上显示的字符是如何显示出来的呢?

  一:字符集(Charset)

charset = char + set,char 是字符,set是集合,charset就是字符的集合。字符集就是是这个编码方式涵盖了哪些字符,每个字符都有一个数字序号。

  二:编码方式(Encoding)

编码方式就是一个字符要怎样编码成二进制字节序,或者反过来怎么解析。也即给你一个数字序号,要编码成几个字节,字节顺序如何,或者其他特殊规则。

  三:字形字体(Font)

根据数字序号调用字体存储的字形,就可以在页面上显示出来了。所以一个字符要显示出来,要显示成什么样子要看字体文件。

   综上所述,Unicode 只是字符集,而没有编码方式。UTF-8 是一种 Unicode 字符集的编码方式,其他还有 UTF-16,UTF-32 等。而有了字符集以及编码方式,如果系统字体是没有这个字符,也是显示不出来的。

【拓展】

(一)字符串与编码

    计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295。

   由于计算机是美国人发明的,因此,最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。

但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。

   你可以想得到的是,全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。

    因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。

   现在,捋一捋ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。

字母A用ASCII编码是十进制的65,二进制的01000001;

字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;

汉字中已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。

你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001。

   新的问题又出现了:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。

   所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:

A0100000100000000 0100000101000001
x01001110 0010110111100100 10111000 10101101

   从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

   搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:

Unicode编码详解

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:

Unicode编码详解

所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正是用的UTF-8编码。

(二)字符编码中ASCII、Unicode和UTF-8的区别

1. ASCII码

我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

ASCII码一共规定了128个字符的编码,比如空格"SPACE"是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

2、非ASCII编码

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

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

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

中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

3.Unicode

世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。

Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字"严"。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。

4. Unicode的问题

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字"严"的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题

第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?

第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。2)Unicode在很长一段时间内无法推广,直到互联网的出现。

5.UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。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编码。

已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。

.

相关推荐