Skip to main content

2 posts tagged with "encoding"

View All Tags

ASCII 和它的朋友们

· 8 min read

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)想必大家都很熟悉了。在远古时期,字符集和编码还没有分的那么开(这也是为什么如今很多人分不清的原因),ASCII 既是字符集,也是编码。

  • 作为字符集,它包含了 33 个控制字符和 95 个可打印(printable)字符,对应 0~127 共 128 个码位。
  • 作为编码,它使用字节的低七位表示字符的码位,因此 ASCII 编码的字节序列中每个字节都与一个字符对应。

需要注意的是,字符的分类从实际的性质和区块(block)的划分上是有所不同的:

  • ASCII 的码位 0~31 是控制字符没错,但不要忘了 127 DEL 也是控制字符。32 空格是不是控制字符存在争议。

  • 码位 0-31 的字符被划分为 C0 Controls(C0 控制字符),码位 32-127 的字符被划分为 Basic Latin(基本拉丁字母)。

可以发现 C0 并没有包含 ASCII 所有控制字符,Basic Latin 下除了拉丁字母还有数字、标点符号等,但区块就是这么分的。

ISO 646

ISO 646 是 ASCII 的国际标准版本。但是考虑到不是所有国家都使用美国的标点符号,因此规定,各个国家可以根据自己的需要,将下面这些符号替换为其它的字符。下表列出中日英德的四个变体:(其中中国的国家标准即 GB 1988)

ASCII-US!"#$&:?@[\]^_`{|}~
CN!"#¥&:?@[\]^_`{|}
JP!"#$&:?@[¥]^_`{|}
GB!"£$&:?@[\]^_`{|}
DE!"#$&:?§ÄÖÜ^_`äöüß

不要觉得德国的看上去很魔幻,实际上大部分欧洲国家皆是如此,只是这里不便列出更多。这也揭示了 C 语言的双字符、三字符还有头文件<iso646.h>的由来,参见这里

时至今日,你仍然能看到不少日本的计算机的反斜杠被显示为日元符号——不过比起软盘和传真机,这倒也不是不可以接受就是了。

扩展 ASCII

ISO 646 推荐的直接修改已分配的 ASCII 码位的字符的做法似乎确实不妥。因此我们逐渐转向扩展 ASCII——毕竟 0x80~0xFF 的码位还未分配嘛。

扩展 ASCII 编码一共讨论、公布了 15 种方案,并被标准化为了 ISO 8859-n,其中 n 为数字 1、2、3、4、5、6、7、8、9、10、11、13、14、15、16 中的一个。(别看了,缺的是 12)

0x80~0xFF 是如何被分配的呢?为了跟 0x00-0x7F 长得像一点,它也分为两个区块:

  • 0x80-0x9F 是 C1 Controls(C1 控制字符)。C0 和 C1 Controls 由 ISO 6429 标准规定。

  • 剩下的码位 0xA0~0xFF 为一个区块,如下表所示:

ISO 8859-n区块名称说明
ISO 8859-1Latin-1西欧语言
ISO 8859-2Latin-2中欧语言
ISO 8859-3Latin-3南欧语言
ISO 8859-4Latin-4北欧语言
ISO 8859-5Cyrillic斯拉夫语言
ISO 8859-6Arabic阿拉伯语
ISO 8859-7Greek希腊语
ISO 8859-8Hebrew希伯来语
ISO 8859-9Latin-5土耳其语
ISO 8859-10Latin-6北日耳曼语族
ISO 8859-11Thai泰语
ISO 8859-13Latin-7波罗的语族
ISO 8859-14Latin-8凯尔特语族
ISO 8859-15Latin-9芬兰语
ISO 8859-16Latin-10罗马尼亚语

别担心,我们不关心上面所有的方案——被运用的最广泛的方案,毫无疑问的,便是 ISO 8859-1。后来它也成为了 Unicode 的一部分。

番外:EBCDIC

ASCII 并非没有竞争者。在那个勃勃生机万物竟发的年代,ASCII 只是众多字符集中的一个。

EBCDIC(Extended Binary Coded Decimal Interchange Code,扩展二进制编码十进制交换代码)是 IBM 推出的一套字符编码,起源于 BCD 码和穿孔卡片(punched cards),在当时也有一定的影响力。

然而它的缺点很明显:拉丁字母不是连续排列的,中间间断了多次,这给使用带来了极大地不便;默认支持的可打印字符较少,且被松散地排列在 0x40 到 0xFF 之间,剩下不连续的空位留给扩展字符,使得扩展与非扩展字符混杂在一起;互不兼容的诸多扩展版本让本就凌乱的标准雪上加霜;混乱的设计更是让与 ASCII 的兼容无从谈起。

Professor: "So the American government went to IBM to come up with an encryption standard, and they came up with—"

Student: "EBCDIC!"

教授:“美国政府造访 IBM,让他们提出一套加密标准,他们提出了——”

学生:“EBCDIC!”

—— the Unix fortune file of 4.3BSD Reno (1990)

最终 ASCII 获胜了,则而 EBCDIC 消失在了历史的长河之中。

参考资料

字节字符字素字字珠玑

· 7 min read

什么是字符集?

字符的集合就叫字符集(character set)。字符在字符集中分配的编号叫做码位或码点(code point)。

例如 S={你,好,世,界}S=\{\text{你,好,世,界}\} 就是一个只有四个字符的字符集。我们可以给这四个字符的分配码位 0,1,2,30,1,2,3,但码位的分配实际上不一定连续或从零开始,因此也可以是 2,3,5,72,3,5,7,或是任意四个互不相同的自然数。但要注意,字符相同,但是字符对应码位不同的字符集不是相同的字符集。

什么是编码?

字符集中的字符(character)与码元(code unit)序列之间的映射 ff 叫字符集的编码(encoding)。同一个字符集可以有多个编码方案。

码元是编码序列的最小单位,被作为一个整体识别。可以是一个字节(byte),也可以是多个字节。

例如我们给出一些上面提到字符集可能的编码方案:

编码结果(十六进制)一个字符包含码元数一个码元包含字节数采用该模式的编码
00010203一个一个ASCII
0001 0001 01 0001 01 01 00一个或多个一个UTF-8
000000011000 00001000 0001一个或多个两个UTF-16

需要注意,当码元为多个字节时,还需要考虑大小端(endianness)时不同的字节表示。例如 0001 可能被表示为 00 01 (BE)或者 01 00 (LE)的字节序列。

上面的例子表明:

  • 编码将字符映射到的是码元序列,不只是单个码元,更不是单个字节。
  • 字符的码位和编码是两个不同的概念,但字符的码位可以作为编码的依据。
  • 同一码位的字符在不同编码方案下得到的码元序列可能不同。
  • 同一码元在不同大小端的模式下得到的字节序列也可能不同。

什么是解码?

中文里,编码不仅是名词,也是动词。动词编码(encode)的反义词是解码(decode)。其反应的过程如下所示。

字符是编码、解码的最小单位。即对于字符序列 c1c2cnc_1c_2\dots c_{n} 有且仅有一个对应的字节序列 f(c1)f(c2)f(cn)f(c_1)f(c_2)\dots f(c_{n}),因此编码器(encoder)只需要实现一个字符到字节序列的编码即可实现字符序列的编码。同理,解码器(decoder)也只需要解码出字节序列的首个字符即可实现字节序列的解码。

什么是字素?

字素(grapheme),又可译作字形,是文字、符号渲染的最小独立单位。

与日常生活中的定义不同,在计算机中,字符不一定是代表某个固定的字素的文字、符号,下面举两类例子说明。

控制字符

控制字符(control character)一般不会直接渲染出字素,但往往能改变其它字符的渲染行为,例如换行符、制表符、从右往左符等;或者帮助计算机对字符串进行识别,如 NUL、BOM 等。

组合字符

组合字符(combining character)可以与其它字符组合成一个字素。例如字素 Å,实际上是由字符 A 和组合字符 ̊ 组合而成。类似地,👩🏽 是由 👩 和 🏽 组合而成。你可以用 python 来简单验证这一点:

>>> len("Å")
2
>>> 'A'+'̊'
'Å'

当组合字符紧随能与之组合的字符时,就可能被渲染器渲染为一个字素,且这样的组合是可以进行多次的。例如 👨‍❤️‍💋‍👨 = 👨+ZWJ+❤+VS-16+ZWJ+💋+ZWJ+👨。(没错,男男亲嘴)

上面的例子表明:一个字素可能包含多个字符。而一个字符又可以编码成多个字节,最后编码形成字节序列的过程可能如下图所示:(数字为十六进制)

总结

综上所示,最后我们梳理一下可能存在的套娃关系:

  • 一个字素包含一个或多个字符
    • 一个字符(或者说它的码位)会被编码成多个码元

      • 一个码元会被编码成一个或多个字节
warning

那么,字符串的长度(length)到底是字节数,字符数,还是字素数呢?Java、C#、JavaScript 等语言给出了掷地有声的回答:都不是!这些语言字符串的 length 都是 UTF-16 的码元数。这是唯一没出现在标题里的层级,想不到吧。

当然,最简单的情况是,一个字素包含一个字符包含一个码元包含一个字节。对,就是 ASCII。参见我的姊妹篇