Unicode

原创
2015/11/01 17:20
阅读数 461

字符集


Unicode 是描述字符对应**码位(code point)**的一种标准。码位是一个整数值,一般写成 16 进制(U+hhhh)。正如 #66ccff 代表数字色彩空间中的一种特定颜色一样,一个 Unicode 码位就可以代表一个字符。Uni 前缀标明了其试图统一全球字符集标准的设计目标。因为过去一段相同的字节码在不同的字符集下可以被解码为不同文本这种事实在是太糟糕了,尤其在互联网时代来临以后。

Unicode 是兼容 ASCII 的。

编码(encoding)


在 Unicode 标准制定好以后,接下来的任务就是制定**编码(encoding)**标准了。如现在互联网上最流行的 UTF-8(8-bit Unicode Transformation Format)。如果说 Unicode 负责将字符转换为整数,那么 UTF-8 的职责就是将整数转换为二进制/字节流。

但我们知道 Unicode 码位其实是可以直接转换为二进制的(不过是进制转换而已)。比如 ASCII 就不需要另一种专门的编码方式。但这种最直观的转换方式会带来一些问题:

  1. 字节序的问题
  2. 空间利用率的问题
  3. 过多连续的 0 会给某些基于文本的网络协议带来麻烦

UTF-8 是一种变长编码,其字节分配规则为:

  1. 码位区间为(U+0000, U+007f)时,即 Latine 字符集,使用一个字节直接表示。这样可以兼容 ASCII
  2. 码位区间为(U+0080, U+07ff)时,即其他字母类字符集,使用两个字节,且每个字节的范围为 128~255,即高位第一位总是 1.
  3. 码位区间为(U+0800, U+ffff)时,即剩余 BMP 字符集(含 CJK),使用 3 个字节,每个字节的范围仍为 128~255.
  4. 码位区间大于 U+ffff 的生僻字符,则使用 4 个字节表示

可见在码位超出 127 时,即多字节编码的每个字节都是以 1 开头,这是为了避免与 ASCII 编码混淆。另外含有主要汉字的 CJK 字符集被分配在了三字节区间。

接下来说明 UTF-8 多字节编码的具体规则。

因为 UTF-8 编码后的字节流的字符与字符之间是有界的(这是UTF-8的一项重要特性),而单一字符的编码字节数又是不定的,所以可以想到,任意两个字符编码的首字节和末字节间必然有明显的差异。为了便于解释这种差异,我们暂时把一个字符编码的第一个字节称为其首字节,其余字节称为其体字节

首字节使用高位的 n 个 1 和紧接着的 1 个 0 来表示当前字符编码总共占用 n 个字节。之后的 n-1 个体字节则全部以 10 打头。因为多字节编码至少占用两个字节,所以任何首字节其实都不会有 10 打头的情况,这样首字节就和体字节区分开了。

一个字符编码中除去这些高位的 1 和紧接着的一个 0 以外的位,都用来存储这个字符的码位。我们不妨称之有效载荷,举个例子:

如汉字的 ,其 Unicode 码位为 U+4f60。如果你使用 Python2,可以这样试一下

lang:python2
>>> a = u'你'
>>> a
u'\u4f60'

强调 Python2 是因为 Python3 默认使用 Unicode 编码字符串,已经没有 unicode 类型了。

然后我们把它编码成 utf-8 字节流:

>>> a.encode('utf-8')
b'\xe4\xbd\xa0'

Python 使用 \x 前缀加两个十六进制数表示一个字节,如 \xff 代表 11111111

可以看到 字的 UTF-8 编码占三个字节。展开成二进制分别为:11100100 10111101 10100000。根据之前的规则,将三个字节的有效载荷拼在一起就是 0100111101100000,转换为 16 进制正好还原为 0x4f60。这个过程就是**解码(decoding)**的过程。

>>> b'\xe4\xbd\xa0'.decode('utf-8')
u'\u4f60'
展开阅读全文
打赏
0
5 收藏
分享
加载中
打赏
2 评论
5 收藏
0
分享
返回顶部
顶部