在混沌的远古时代,并没有什么通用的字符集,而ASCII字符集作为一种美帝根据自身情况提出的字符集,整个字符集表堪堪128个位置。显然这并不能满足其他地区和国家的字符存储与处理需求,故而各个地区与国家纷纷根据自身的字符需求,制定字符集格式。

其实介绍各种编码的文章很多很多(毕竟那么多人被坑过),在本文里我依旧想从我的角度简单介绍一下,额外的会有一点点Unicode的豆知识。

如果你打开了这个页面,那么你看到的这个页面的所有文字都是UTF-8字符集(Encoding)的,不缺字也没有乱码。

群雄割据

不管是什么字符集,其本质都是规定了一个码表,指定了某一个数值来对应一个字符。

  ASCII GBK Unicode
A 65 65 65
  ASCII GBK Unicode
/ 59718 38229
  ASCII GBK Unicode
𠙶 / / 132726

一个字符要被显示,字符集与字形两者缺一不可

以上三个字符,在字符不断地发展过程中,逐渐被赋予显示的机会。

另一方面,当前的计算机架构下,我门并不能直接将这样的数字存至硬盘中。上面提及的是字符映射到数值的过程。数值还需要映射到比特序列,这样才能在硬盘中以以字节为最小单位将数据进行存储。这第二个映射,就是字符编码。

字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个字节的方式存储。

而一个字节满打满算也就256个位置,中文怎么办呢,你可能会说,扩充到两个字节呗。没错,GBK、Shift_JIS、BIG5等等我们以前曾经见到的编码格式都是这么做的,为了做到向前兼容,它们中既存在单字节编码也存在双字节编码。可惜当时的设计都是以够用为基础的,同IPv4一样, 如果双字节就能完全解决的话,就不会有那么多的幺蛾子了。

两个字节相比一个字节,确实多了很多位置,达到了海量的65536个位置,且不论这些位置对于各自编码对应的国家是否够用,这各国的编码都没有充分地考虑到其余各国的字符,也即无法做到一个编码走天下。举个例子,在GBK编码中天城文(ओ)就没有机会显示。

Unicode

时值1991年,统一码联盟首次发布了The Unicode Standard,旨在使用一种编码来表示世界上几乎所有的文字。现今Unicode中设置了17个编码平面每一个平面都拥有65536个代码点,并且其中大部分平面尚未被使用,拥有了良好的扩展性。

那么接近30年的发展,虽然由于一些历史遗留问题,我们仍然会在Windows中见到一些使用了本地代码页的情况,但是在诸如开发过程中,我们往往都会优先考虑去用UTF-8(有没有BOM不讨论)去存储文本。

那么问题来了,UTF-8/UTF-16这些又是个什么呢,和Unicode又有什么关系呢?这两者,其实都是是一种针对Unicode可变长度字符编码。没错,UTF-16也是可变长的,由于Unicode的设计最高为32bit,故对于UTF-16,最多会使用两个码元来进行表示,并且由于UTF-16的一个码元中包含两个字节,故其还有大端、小端两种形式,一般来说,以Macintosh制作或存储的文字使用大尾序格式,以Microsoft或Linux制作或存储的文字使用小尾序格式。

新年号令和

回首2019年的愚人节,日本的新年号令和堂堂公布,这里不去讨论这个年号好不好听,也不说这对程序员要有多大的伤害。其实我这里要说的是,在Shift-JIS中,有着大量的日本搞的合字的存在,比如㌐、㌏、㌎、㌻、㍄,这些个字符,在Unicode中也有相应的收录。很巧,年号也有。

  A B C D E F
U+3370

很遗憾,没有给未来的年号留位置啊,难不成以后都显示㍺元年或者是㍿元年么?其实呢,日本显然也是有意识到这个问题,以仍有大量的旧有的大型商业系统中依旧依赖这一系列的字符并且往往仅支持BMP为由,于2017年12月向Unicode组织发出过申请,并且经过组织讨论,决定将U+32FF这个位置作为新年号的编码,不知道什么时候字体更新了能看到下面这个年号呢。

  令和
U+32FF

组合字

为什么会提及说

We noticed some legacy solutions still only support BMP.

因为在Unicode中也有一套机制来构成各种的组合字,用于支持一些复杂的语言,如阿拉伯语。而这个特性,也能达成上述的效果。

ё(U+0451)是一个单独的编码,但它也可以通过е(U+0435) + ̈(U+0308)组合成为ё来进行显示。

(U+304C)也可以通过(U+304B) + (U+3099)组合成为が来进行显示。

当然还有各种复杂的组合形式,在emoji中也有相关的应用,具体可自行寻找资料学习。

而这样的特性,对人而言是无感的,对计算机就不大对劲了。如果比较两段文字是否相同,甚至是文件夹下是否有重复的文件,这就是一个问题了(高贵的Apple使用的往往就同主流不一样,不论是过去的换行,还是这里出现的UTF8-MAC)。

对于这个情况,Unicode下共有这里有4种正规形式 (normalization forms) ,它们都把Unicode字符转换成对应标准格式,从而使得能够把所有的字符进行字节(byte)级别的比较,大致如下表。

名称 中文名称 描述
NFD Normalization Form Canonical Decomposition 正规形式D 字符通过规范形式进行分解
NFC Normalization Form Canonical Composition 正规形式C 字符通过规范形式进行分解并重新组合。字符序列可能在转换前后变化
NFKD Normalization Form Compatibility Decomposition 正规形式KD 字符通过兼容形式进行分解
NFKC Normalization Form Compatibility Composition 正规形式KC 字符通过兼容形式进行分解,并以规范形式重新组合

以㍻为例可以大致看一下结果。

名称 原始文字 正规化后
NFD
NFC
NFKD 平成
NFKC 平成

额外正规化内容可参见这里

小结

其实没有小结,毕竟Unicode特性过于丰富,要是去深究的话,我怕是也研究不清楚,以上也仅仅是将自己所知强迫自己以文字形式记录下来(甚至整篇文章的术语也没有经过统一),这篇拖了好几个月也终于在清明最后一天算是完成了。

扩展阅读

Unicode字符集与UTF-8编码

Unicode 是不是只有两个字节,为什么能表示超过 65536 个字符?

其实你并不懂 Unicode

为什么 Sketch 导出 PDF 会导致某些字符的 unicode 发生变化?

Macでファイル名をコピペすると濁点と半濁点がおかしくなる