字符编码发展史4 — Unicode与UTF-8

上一篇《字符编码发展史3 — GB2312/Big5/GBK/GB18030》我们讲解了ANSI编码中的GB2312/Big5/GBK/GB18030。本篇我们将继续讲解字符编码的第三个发展阶段中的Unicode与UTF-8。

2.3. 第三个阶段 国际化

前面提到的第二个阶段,各个国家和地区各自为政,纷纷制定了适用于自己国家语言的字符编码(统称为ANSI码),确实能解决该地区范围内语言文字的信息化处理。

随着互联网的普及和全球网络的互联互通,计算机的信息经常需要在全球范围内进行分享和传输。这时这些只兼容ASCII码互相之间却不兼容的字符编码就暴露了巨大的缺陷:编码混乱,这个混乱常体现在以下几点:

  1. 文本信息是一个国际化的内容,包含了多种不同的语言时,根本找不到一个合适的编码。如:你的内容里既有西欧的法语又中国的汉字,包含西欧语言的ISO 8859-1不支持中国的汉字,包含中国汉字的GB 18030不支持西欧的字符。
  2. 编码和解码使用的编码方式不一致时,会出现乱码。如以下两种场景:
  • 数据在网络传输时,数据发送用了A编码(假设是ISO 8859-1),数据接收时误用了B编码(假设是GB 18030)去解码,就会出现乱码。
  • 网上下载了一个纯文本的txt文档,里面保存内容的编码方式和本地计算机的默认编码不一致也会出现乱码。这时你可能还根本不知道这个文档采用的编码是什么,只能靠猜测,然后通过工具去手动转换编码格式。

为了解决ANSI系列编码的缺陷,使国际间信息交流更加方便,国际标准化组织(ISO)和统一码联盟(Unicode Consortium)共同制定的一个国际标准字符集:Unicode。Unicode为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。

2.3.1. Unicode与UCS

2.3.1.1. 什么是Unicode与UCS?

这里讲一个冷知识,历史上存在两个独立的尝试创立单一字符集的组织,即 国际标准化组织(ISO)和统一码联盟(Unicode Consortium)。

  • 国际标准化组织 制定了UCS标准(全称Universal Character Set),最初称为ISO/IEC 10646。
  • 统一码联盟 制了Unicode标准,旨在解决不同字符编码之间的兼容性问题。

随着时间的推移,国际标准化组织和统一码联盟意识到各自的标准在目标上是一致的,因此决定合作,将UCS和Unicode合并为一个统一的标准。从Unicode 2.0开始,Unicode标准与ISO/IEC 10646标准保持同步,两者在字符集和编码方案上基本一致。

所以,你可以理解为:Unicode和UCS是同一个东西:国际标准字符集。现在几乎统一用Unicode一词,UCS用的越来越少了。

Unicode是一个字符集,不是编码方式,又称统一码万国码单一码标准万国码(其实都是同一个东西,不同的叫法)。它收集了世界上几十种文字系统,几乎包含了世界上用到的所有字符。截止2024年9月,Unicode的最新的版本是16.0.0,发布于2024年9月10日,总共收录了154,998个字符。Unicode 16.0.0标准的官方文档参见:https://www.unicode.org/versions/Unicode16.0.0/

Unicode的编码方式有三种:UTF-8、UTF-16、UTF-32。其中UTF-16、UTF-32又分为大端和小端两种。

2.3.1.2. Unicode字符集的码点编号

Unicode字符集给每个字符根据其所在的码点分配了一个唯一的码点值,即码点编号,也叫字符编号,格式为:U+XXXX,其中XXXX为四位十六进制数字。比如,U+0041这个码点编号,表示英语大写字母A

Unicode的编码空间将所有字符按照使用的频率划分为17个平面(plane),每个平面包含2^16(65536)个码位,将来根据需要,还可扩展为更多平面。17个平面的码位可表示为从U+0000U+10FFFF,共计1114112个码位。

第0个平面称为基本多语言平面(Basic Multilingual Plane),简称基本平面(BMP),或称第零平面(Plane 0),码点区间:U+0000~U+FFFF。它涵盖了当今世界上正在使用的最常用字符,我们平常用到的大多数常见字符,就是在BMP平面上。BMP平面以外的其他平面叫增补平面(Supplementary Planes),也称为辅助平面

Unicode字符集中的U+0000~U+007F(即十进制的0~127),跟ASCII表示的字符是一致的;U+0000~U+00FF(即十进制的0~255),跟ISO 8859-1字符集(即Latin-1字符集)也是一致的。所以Unicode的码点编号是兼容ASCII和ISO 8859-1的。

BMP平面中有一个私用区(即PUA:Private Use Area,或写作PUZ:Private Use Zone):0xE000~0xF8FF,共6400个码点,被保留为私用,Unicode官方未将之分配给任何Unicode字符。还有一个代理区(Surrogate Zone):0xD800-0xDFFF,共2048个码点,代理区的码点不定义任何字符,目的是用基本平面BMP中的两个码点“代理”表示BMP以外的其他增补平面中的字符(后文UTF-16中会详细讲解)。

Unicode实际上共定义了三个私用区,除了上面提到的BMP的0xE000~0xF8FF,还有两个分别是:第15平面的U+F0000~U+FFFFD和第16平面的U+100000~U+10FFFD,这两个私用区几乎包含了整个第15平面和第16平面。私用区相当于是可以由Unicode官方之外的个人和机构自由定义字符的特殊区域,因此私用区中的同一个码点,可被分配给不同的字符,具体是哪个字符,取决于用户使用的字体文件,从而导致不同的用户由于安装了不同的字体文件,有可能所看到的私用字符也不同。

2.3.2. Unicode的编码方式

对于被Unicode收录的字符其编号(即码点编号)是唯一且确定的。但是Unicode的编码实现方式(出于传输、存储、处理或向后兼容的考虑)却有不同的几种:UTF-8、UTF-16、UTF-32。其中UTF的全称是:Unicode Transformation Format,表示“Unicode码转换格式”。其中8/16/32分别表示8位(1字节)/16位(2字节)/32位(4字节),表示一个字符进行编码所需的最小字节单元,也称编码单元,简称码元

2.3.2.1. UTF-8

1. UTF-8的编码规则

UTF-8是一种变长编码,对于一个Unicode的字符被编码成1至4个字节。Unicode编码与UTF-8的编码的对应关系如下表。

Unicode编码 十进制表示 UTF-8编码(二进制)
U+0000 – U+007F 0 ~ 127 0xxxxxxx
U+0080 – U+07FF 128 ~ 2047 110xxxxx 10xxxxxx
U+0800 – U+FFFF 2048 ~ 65535 1110xxxx 10xxxxxx 10xxxxxx
U+10000 – U+10FFFF 65536 ~ 2097151 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

0、110、1110、11110以及10相当于UTF-8编码中各个字节的前缀,因此称之为前缀码。他们的含义分别如下:

  • 0: 表示单字节编码,单字节时表明该字符是一个ASCII字符。
  • 110: 表示双字节编码,出现在双字节编码的首字节。
  • 1110: 表示三字节编码,出现在三字节编码的首字节。
  • 11110: 表示四字节编码,出现在四字节编码的首字节。
  • 10: 表示该字符是一个多字节编码(2、3、4字节),10是多字节编码中非首字节的前缀。

UTF-8编码中的前缀码起到了很好的区分和标识的作用,其编码的解析过程大致如下:

  1. 当解码程序读取到一个字节的首位为0,表示这是一个单字节编码的ASCII字符;
  2. 当读取到一个字节的首位为1,表示这是一个非ASCII字符的多字节编码字符中的某个字节(可能是首字节,也可能是后续字节),接下来若继续读取到一个1,则确定为首字节,再继续读取直到遇见终结标志0为止,读取了几个1,就表示该字符为几个字节的编码;
  3. 当读取到一个字节的首位为1,紧接着读取到一个终结标志0,则该字节显然是非ASCII字符的后续字节(即非首字节)。

在UTF-8编码方式中,绝大部分的中文用三个字节编码,部分中文用四个字节编码,举例如下:

Unicode 字符 UTF-8编码
U+0041 A 0x41
U+03A9 Ω 0xCE 0xA9
U+6653 0xE6 0x99 0x93
U+2A6A5 𪚥(四个龍) 0xF0 0xAA 0x9A 0xA5
2. UTF-8的优缺点
  • 优点:
    • 向后兼容ASCII编码;
    • 没有字节序(大小端)的问题适合网络传输;
    • 存储英文和拉丁文等字符非常节省存储空间。
  • 缺点:
    • 变长编码不利于文本处理;
    • 对于CJK等文字比较浪费存储空间。

未完待续…… 欲知后事如何,且看下回分解。

下回预告:字符编码发展史5 — UTF-16和UTF-32。

历史文章推荐:

字符编码发展史3 — GB2312/Big5/GBK/GB18030

字符编码发展史2 — ISO-8859-N

字符编码发展史1 — ASCII和EASCII


大家好,我是陌尘。

IT从业10年+, 北漂过也深漂过,目前暂定居于杭州,未来不知还会飘向何方。

搞了8年C++,也干过2年前端;用Python写过书,也玩过一点PHP,未来还会折腾更多东西,不死不休。

感谢大家的关注,期待与你一起成长。



【SunLogging】
扫码二维码,关注微信公众号,阅读更多精彩内容