刨根究底字符编码之十三——UTF-16编码方式

UTF-16编码方式

1.

UTF-16编码方式源于UCS-2(Universal Character Set coded in 2 octets、2-byte Universal Character Set)。而UCS-2,是早期遗留下来的历史产物。

UCS-2将字符编号(即码点值)直接映射为字符编码(CEF,而非CES,详见前文中对现代字符编码模型的解释),亦即字符编号就是字符编码,中间没有经过特别的编码算法转换。因此,从现代字符编码模型的角度来看的话,此时并没有将编号字符集CCS与字符编码方式CEF作严格区分,既可以将UCS-2看作是编号字符集CCS中的字符编号,也可以看作是字符编码方式CEF中的字符编码。

后来,随着统一码联盟(即Unicode联盟)与ISO/IEC就创建全球统一的单一通用字符集进行合作,Unicode字符集与UCS字符集逐渐相互融合,两者最终基本保持了一致(详见前文《刨根究底字符编码之八——Unicode编码方案概述》中的介绍)。

笨笨阿林原创文章,转载请注明出处)

2.

这之后,Unicode逐渐占据了主导地位,并引入了UTF-16编码方式。为什么要引入UTF-16编码方式呢?

前文已经介绍过了,Unicode字符集(CCS)到目前为止定义了包括1个基本平面BMP(Basic Multilingual Plane基本多语言平面)和16个增补平面SP(Supplementary Plane辅助平面、扩展平面)在内的共17个平面。

每个平面(Plane)的码点(Code Point)数量为2^16=65536个,因此17个平面的码点总数为共65536*17=1114112个。其中,基本平面码点为65536个(码点编号范围为0x0000~0xFFFF),增补平面码点为1114112-65536=65536*16=1048576个(码点编号范围为0x10000~0x10FFFF)。

很明显,简单地用一个16位码元肯定无法表示所有17个平面的这么多码点(因为2^16=65536,而码点总数为65536*17=1114112)。而UCS-2,正是用两个字节共16位来表示一个字符的。为支持字符编号超过U+FFFF的增补字符,扩展势在必行。

3.

UCS因而又提出了UCS-4,即用四个字节共32位来表示一个字符(此时UCS-4同样既可认为是编号字符集CCS中的字符编号,也可认为是字符编码方式CEF中的字符编码)。但码元也因此从16位扩展到了32位。

而Unicode却提出了不同的扩展方式——代理机制。具体而言,就是为了能以一个统一的16位码元同时编码基本平面以及增补平面中的字符码点编号,Unicode设计引入了UTF-16编码方式,并且通过代理机制实现了扩展。

UTF-16编码方式的引入,从现代字符编码模型的角度来看的话,彻底将编号字符集CCS与字符编码方式CEF作了严格区分。也就是说,在UTF-16编码方式中,编号字符集CCS中的字符编号(即码点编号、码点值)与字符编码方式CEF中的字符编码(即码元序列)不再仅仅是简单的直接映射关系。

具体来说,就是Unicode字符集基本平面BMP中的字符(大致相当于UCS字符集中的UCS-2字符,但必须除开U+D800~U+DFFF这一在Unicode字符集BMP中称之为代理码点的部分),仍然是直接映射关系,亦即这部分字符的字符编号(即码点编号、码点值)与字符编码(即码元序列)是等同的。

但Unicode字符集增补平面中的字符(大致相当于UCS字符集UCS-4字符中除开UCS-2字符的部分,因为广义上的UCS-4字符实际包含了UCS-2字符,当然狭义上的UCS-4字符不包括UCS-2字符),却不是直接映射关系,而是必须通过代理机制这一编码算法的转换,亦即这部分字符的字符编号(即码点编号、码点值)与字符编码(即码元序列)不是等同的。

因此,在Unicode引入了UTF-16编码方式之后,站在现代字符编码模型的角度上来看的话,再将UCS-2和UCS-4直接称之为字符编码方式CEF已不是很合适,更多的应该是编号字符集CCS中的概念(当然,在了解其历史原因之后,将UCS-2和UCS-4同时理解为编号字符集CCS和字符编码方式CEF也未尝不可;只是这样一来,对于不了解其历史原因的人而言,将带来理解上的困扰);而若将UCS-2等同于UTF-16,将UCS-4等同于UTF-32(后文会有介绍),显然也是不太合适的。

笨笨阿林原创文章,转载请注明出处)

4.

UTF-16中的所谓代理机制,实际上就是用两个对应于基本平面BMP代理区(Surrogate Zone)中的码点编号的16位码元来表示一个增补平面码点,这两个用来表示一个增补平面码点的特殊16位码元被称之为代理对(Surrogate Pair)(解释详见后文《UTF-16究竟是如何编码的——UTF-16的编码算法详解》)

UTF-16编码方式及其代理机制是在Unicode 2.0中为支持字符编号超过U+FFFF的增补字符而引入的,于是从此就由UCS-2的等宽(16位)码元序列编码方式(如前文所述,从现代字符编码模型的角度来看的话,UCS-2更多是的编号字符集CCS中的概念,但考虑到其历史原因,称之为字符编码方式CEF亦未尝不可,下同,不再赘述),变成了UTF-16的变宽(16位或32位)码元序列编码方式。不过,码元依然保持了16位不变。

5.

UCS-2所编码的字符集中的U+D800~U+DFFF这部分代理码点除外的话,UTF-16所编码的字符集可看成是UCS-2所编码的字符集的父集。

在没有引入增补平面字符之前,UTF-16与UCS-2(U+D800~U+DFFF这部分代理码点除外)的编码完全相同。但当引入增补平面字符后,UTF-16与UCS-2的编码就不完全相同了(事实上,由于UCS-2只有两个字节,根本无法编码增补平面字符)。

现在若有软件声称自己支持UCS-2编码,那相当于是在暗示其仅支持UCS字符集或Unicode字符集中的基本平面字符,而不能支持增补平面字符。

6.

如前所述,UTF-16中的码元由双字节共16位组成。其中,小于U+10000(即二进制1 0000 0000 0000 0000、十进制65536)的码点(字符编号)被映射到单个码元,大于等于U+10000的码点被映射到两个码元;换言之,UTF-16编码方式使用一个或两个16位码元的序列来表示Unicode字符。

UCS-2最初设计的时候只考虑到基本多语言平面BMP(Basic Multilingual Plane)字符,因此使用固定2个字节(16位)长度,也就是说,无法表示Unicode其他平面(即增补平面)上的字符,而UTF-16为了解除这个限制,支持Unicode全字符集的编解码,采用了变长编码,最少使用2个字节(16位),如果要编码BMP以外的增补平面字符,则需要4个字节(32位,共两个16位码元)结对(即结成代理对surrogate pair)表示。

因此,UTF-16中,在U+0000~U+FFFF(即基本平面)的码点值使用一个单一的16位码单元表示(专用区、代理区等特殊区域除外);而在U+10000~U+10FFFF(即增补平面)的码点值则使用一对16位码元(对应于基本平面中的代理区码点值)表示,称作代理对(Surrogate Pair)。(解释详见附文)

所以说,UTF-16从来都是变长编码方式,每个字符编码为2字节或4字节;而UCS-2是定长编码方式。Windows 2000及之后的版本是支持UTF-16的,之前的Windows NT/95/98/ME是只支持UCS-2的。

笨笨阿林原创文章,转载请注明出处)

7.

作为逻辑意义上的UTF-16编码(码元序列),由于历史的原因,在映射为物理意义上的字节序列时,分为UTF-16BE(Big Endian)、UTF-16LE(Little Endian)两种情况。比如,“ABC”这三个字符的UTF-16编码(码元序列)为:00 41 00 42 00 43;其对应的各种字节序列如下:

Windows平台下的UTF-16编码(即上述的FF FE 41 00 42 00 43 00) 默认为带有BOM的小端序(即Little Endian with BOM)。你可以打开记事本,写上ABC,保存时选择Unicode(这里的Unicode实际上指的是UTF-16 Little Endian with BOM,即带BOM的UTF-16小端序CES编码,详见后文解释)

然后保存,再用UltraEdit编辑器看看它的编码结果:

Windows从NT时代开始就采用了UTF-16编码方式,很多流行的编程平台,例如.Net、Java、Qt还有Mac下的Cocoa等都是使用UTF-16作为基础的字符编码。例如代码中的字符串,在内存中相应的字节流就是UTF-16字节序列的。(注意,UTF-16编码在Windows环境中被误用为“widechar”和“Unicode”的同义词)

8.

UTF-16使用二或四个字节为每个字符编码,其中大部分汉字采用两个字节编码,少量不常用汉字采用四个字节编码。

UTF-16一方面使用变长码元序列的编码方式,相较于定长码元序列的UTF-32算法更复杂(甚至比同样是变长码元序列的UTF-8也更为复杂,因为引入了独特的代理对这样的代理机制);另一方面仍然占用过多字节,比如ASCII字符也同样需要占用两个字节,相较于UTF-8更浪费空间和带宽。

因此,UTF-16在Unicode字符集的三大编码方式(UTF-8、UTF-16、UTF-32)中表现较为糟糕。它的存在是历史原因造成的,引起了很多混乱。不过由于其推出时间最早,已被应用于大量环境中,目前虽然不被推荐使用,但长期来看,作为程序人员都不得不与之打交道。因而,对于其具体的编码算法的了解是十分必要的,本系列文章的下一篇将详细介绍其复杂的编码算法(主要是代理编码算法)

笨笨阿林原创文章,转载请注明出处)

(未完待续)

预告:《刨根究底字符编码》系列的下一篇将详细介绍UTF-16复杂的编码算法;而《刨根究底正则表达式》系列的下一篇将正式开始逐个正则表达式语法元素的详解,敬请关注!】

时间: 2024-12-19 10:23:35

刨根究底字符编码之十三——UTF-16编码方式的相关文章

刨根究底字符编码之十四——UTF-16究竟是怎么编码的

UTF-16究竟是怎么编码的 1. 首先要注意的是,代理Surrogate是专属于UTF-16编码方式的一种机制,UTF-8和UTF-32是不用代理的. 如前文所述,为了让UTF-16能继续编码基本平面后面的增补平面中的码点值,于是扩展了UTF-16编码方式. 具体的扩展方法就是为其增加了代理机制,用两个对应于基本平面码点(即BMP代理区中的码点)的16位码元来表示一个增补平面码点,这两个用来表示一个增补平面码点的特殊16位码元就被称为"代理对". 如果要用简单的一句话来概括,就是--

刨根究底字符编码之十一——UTF-8编码方式与字节序标记

UTF-8编码方式与字节序标记 一.UTF-8编码方式 1. 接下来将分别介绍Unicode字符集的三种编码方式:UTF-8.UTF-16.UTF-32.这里先介绍应用最为广泛的UTF-8. 为满足基于ASCII.面向字节的字符处理的需要,Unicode标准中定义了UTF-8编码方式.UTF-8应该是目前应用最广泛的一种Unicode编码方式(但不是最早面世的,UTF-16要早于UTF-8面世).它是一种使用8位码元(即单字节码元)的变宽(即变长或不定长)码元序列的编码方式. 由于UTF-16对

刨根究底字符编码之十二——UTF-8究竟是怎么编码的

UTF-8究竟是怎么编码的 1. UTF-8编码是Unicode字符集的一种编码方式(CEF),其特点是使用变长字节数(即变长码元序列.变宽码元序列)来编码.一般是1到4个字节,当然,也可以更长. 为什么要变长呢?这可以理解为按需分配,比如一个字节足以容纳所有的ASCII码字符,那何必补一堆0用更多的字节来存储呢? 实际上变长编码有其优势也有其劣势,优势是节省空间.自动纠错性能好.利于传输.扩展性强,劣势是不利于程序内部处理,比如正则表达式检索:而UTF-32这样等长码元序列(即等宽码元序列)的

刨根究底字符编码之十——Unicode字符集的字符编码方式CEF以及码点、码元

Unicode字符集的字符编码方式CEF以及码点.码元 一.字符编码方式CEF的选择 1. 由于Unicode字符集非常大,有些字符的编号(码点值)需要两个或两个以上字节来表示,而要对这样的编号进行编码,也必须使用两个或两个以上字节. 比如,汉字"严"的Unicode码(Unicode码点值.Unicode编号)是十六进制数4E25,转换成二进制数有15位(100 1110 0010 0101),对"严"这个字符的编号进行编码的话,至少需要2个字节.表示其他更大编号

刨根究底字符编码之二——关键术语解释(下)

关键术语解释(下) 一.第1层 抽象字符表ACR (Abstract Character Repertoire抽象字符清单):明确字符的范围(即确定支持哪些字符) 1. 抽象字符表ACR是一个编码系统支持的所有抽象字符的集合,可以简单理解为无序的字符集合,用于确定字符的范围,即要支持哪些字符. 抽象字符表ACR的一个重要特点是字符的无序性,即其中的字符并没有编排数字顺序,当然也就没有数字编号. 2. "抽象"字符不具有某种特定的字形,不应与具有某种特定字形的"具体"

刨根究底字符编码之八——Unicode编码方案概述

Unicode编码方案概述 1. 前面讲过,随着计算机发展到世界各地,于是各个国家和地区各自为政,搞出了很多既兼容ASCII但又互相不兼容的各种编码方案.这样一来同一个二进制编码就有可能被解释成不同的字符,导致不同的字符集在交换数据时带来极大的不便. 比如大陆和台湾是只相隔150海里.使用着同一种语言的兄弟地区,也分别采用了不同的DBCS双字节字符集编码方案. 以前大陆地区必须装上类似于"UCDOS希望汉字系统"这样的中文处理系统专门来处理简体汉字的显示.输入问题. 而台湾地区由于采用

刨根究底字符编码之五——简体汉字编码方案(GB2312、GBK、GB18030、GB13000)以及全角、半角、CJK

简体汉字编码方案(GB2312.GBK.GB18030.GB13000)以及全角.半角.CJK 一.概述 1. 英文字母再加一些其他标点字符之类的也不会超过256个,用一个字节来表示一个字符就足够了(2^8 = 256).但其他一些文字不止这么多字符,比如中文中的汉字就多达10多万个,一个字节只能表示256个字符,肯定是不够的,因此只能使用多个字节来表示一个字符. 于是当计算机被引入到中国后,相关部门设计了GB系列编码("GB"为"国标"的汉语拼音首字母缩写,即&q

刨根究底字符编码之一——关键术语解释(上)

声明:本系列文章参考了网上的大量资料,除了少部分资料由于未作大量修改(但基本上也有少量修改,因为网上文章随意性较大,很多明显的笔误或前后矛盾之处,如若不改反而让人迷糊)而标明了原作者和出处之外,其余由于基本上已按自己的理解作了大量改写,因此没有再一一予以说明,在此对原作者表示歉意并感谢.另外,文中图片来自网络,也不在一一说明.同时,文中若有错漏,还请直接招呼板砖,不用客气. 关键术语解释 位: 即比特(Bit),亦称二进制位.比特位.位元.位,指二进制数中的一位,是计算机中信息表示的最小单位.B

更改python字符编码以便使用UTF-8的编码url路径

url编码分两种, 一种是unicode, 另一种是gb2312, 今天遇到的一个网站是要将字符编码按照gb2312来编码,用来得到一个先填写blanks后再返回页面的数据,废话少说,需要做的就是先查看你的python的编码是啥,默认是ascii, ? 1 2 3 import sys print sys.getdefaultencoding() # 'ascii' 直接附代码吧: ? 1 import urllib as com ? 1 2 3 4 5 6 7 8 9 10 11 12 13