以前对「字符集与编码」的认识完全来源于廖雪峰的这篇文章,但是总容易忘记
而且依稀记得很早的时候在知乎看到梁海大神提到过字符集与编码是不一样的概念
今天仔细查阅了一下,记录如下
字符集与编码
顾名思义,「字符」的「集合」,每个「字符」有一个唯一的「数字」与之对应
而如何将「数字」改写成内存中的「字节」,即所谓的「编码」或「编码方式」
「字符集」和「编码」等几个层次的概念被彻底分离且模块化的这样一个模型,其实是 Unicode 时代才得到广泛认同的。
而对于 ASCII、GB2312、Big5 之类的遗留(legacy)方案,其字符集及其编码的关系基本是锁定的,所以常常用「字符编码」(character encoding)、「代码页」(code page)等概念来统称它们那样从字符到编码字节流的整体方案。比如 ASCII 本身就既是字符集又是编码方案,而 GB2312 用的都是 EUC-CN 编码方案。
ASCII
ASCII 既是字符集又是编码方案,其字符集为「它所能表示的 128 种字符(包括控制字符和显示字符)」,编码方案为「128 个字节码与字符集间的映射」
ANSI
ASCII 本身就是 ANSI(American National Standard Institute) 制定的标准。虽然 ASCII 使用一个字节即 8 位的长度,但由于在英文世界中 7 位已经完全够用,所以最高位始终为 0。其他国家便将最高位利用起来,用于扩展表示其他语言的字符。
ANSI 指的是这样一种编码方式:表示通用英文字符时,最高位为 0,使用 ASCII 编码,长度为一个字节;表示其他语言的专有字符时,最高位为 1,使用其他编码。其中「其他编码」可以是 ANSI 标准中的任意一页,比如「GB2312」,用两个最高位为 1 的字节来表示一个简体中文字符。
Unicode 字符集
Unicode 原本只是个字符集,freebuf 的一篇译文讲得很生动:
Unicode 背后的想法非常简单,然而却被普遍的误解了。Unicode 就像一个电话本,标记着字符和数字之间的映射关系。Joel 称之为「神奇数字」,因为它们可能是随机指定的,而且不会给出任何解释。官方术语是码位(Code Point),总是用 U+ 开头。理论上每种语言中的每种字符都被 Unicode 协会指定了一个神奇数字。例如希伯来文中的第一个字母 א,是 U+2135,字母 A 是 U+0061。
Unicode 并不涉及字符是怎么在字节中表示的,它仅仅指定了字符对应的数字,仅此而已。
关于 Unicode 的其它误解包括:Unicode 支持的字符上限是 65536 个,Unicode 字符必须占两个字节。告诉你这些的人应该去换换脑子了。
记住,Unicode 只是一个用来映射字符和数字的标准。它对支持字符的数量没有限制,也不要求字符必须占两个、三个或者其它任意数量的字节。
Unicode 编码
Unicode 的实现方式不同于编码方式。一个字符的 Unicode 编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对 Unicode 编码的实现方式有所不同。Unicode 的实现方式称为 Unicode 转换格式(Unicode Transformation Format,简称为 UTF)。
UTF-8 编码
如果一个仅包含基本 7 位 ASCII 字符的 Unicode 文件,如果每个字符都使用 2 字节的原 Unicode 编码传输,其第一字节的 8 位始终为 0 。这就造成了比较大的浪费。
对于这种情况,可以使用 UTF-8 编码,这是一种变长编码,它将基本 7 位 ASCII 字符仍用 7 位编码表示,占用一个字节(首位补 0)。
而遇到与其他 Unicode 字符混合的情况,将按一定算法转换,每个字符使用 1-3 个字节编码,并利用首位为 0 或 1 进行识别。这样对以 7 位 ASCII 字符为主的西文文档就大大节省了编码长度(具体方案参见 UTF-8)。
UTF-16 编码
类似的,另一个流行的可变长度编码方案是 UTF-16,它使用 2 个或者 4 个字节来存储字符。然而,人们逐渐意识到 UTF-16 可能会浪费存储空间,但那是另一个话题了。
狭义的 Unicode 编码
需要注意的是,狭义的 Unicode 编码方式与 ISO 10646 的通用字符集(Universal Character Set,UCS)概念相对应,目前实际应用的 Unicode 版本对应于 UCS-2 ,使用 16 位的编码空间。也就是每个字符占用 2 个字节。
这样理论上一共最多可以表示 216 即 65536 个字符。基本满足各种语言的使用。实际上当前版本的 Unicode 尚未填满这 16 位编码,保留了大量空间作为特殊使用或将来扩展。
以 Python 为例,unicode(s,'utf8')
所返回的结果就是这种狭义的 Unicode 编码。
又是梁海在廖雪峰答案的评论里提到:
廖雪峰说的「Unicode」指的是 UCS-2(所以不能改成「UTF-32」)。当年只有 BMP 的时候 UCS-2 和 UTF-16 是等价的,又被称作「Unicode」(所以这里不是「Unicode 是字符集,不是编码方式」这个问题,只是 UCS-2 也被称作「Unicode」而已)。但现在这种用「Unicode」指代 UCS-2/UTF-16 的做法已经过时了,很不合适。
总结
简单总结一下,Unicode 是「字符集」而不是「编码」。但由于历史原因,某些时候提到的「Unicode 编码」指的是曾经定长的 UCS-2/UTF-16(现在 UTF-16 也是变长)。
由于 UTF-8 可以兼容 ASCII,且其变长特性能相对高效地利用空间,在应用或传输过程中一般使用的是 UTF-8。例如 Python shell 中 str 使用的就是 ASCII(UTF-8)。
而内存一般使用的是这种「Unicode 编码」,所以在进行编码转换时,通常将各类编码decode
成「Unicode 编码」,在encode
成最终编码形式。
eg:
|
在 Python 2.x 版本中,字符串实际上是字节串,这种字符串中的每个字符长度为 1,实际上就是一个字节,等同于 ASCII。(参考《Python Cookbook》1.21)
不过在 Python 3.x 版本中,把 ‘xxx’ 和 u’xxx’ 统一成了 Unicode 编码,即写不写前缀 u 都是一样的,而以字节形式表示的字符串则必须加上 b 前缀。