字符编码解码总结
前言
字符编码解码这个问题一直困扰着诸多的程序开发者,可能也查阅过不少资料,可在实际编码工作中对于一些问题还是一知半解。
例如笔者在最初学习时,知道编码是将字符转换为二进制码,但似乎由觉得Unicode也是码,可资料又说在某些语言如Python中,Str是字符串,也是Unicode,可字符串怎么又可能是’码‘呢?这些疑问一直迷惑着笔者。好在最后经过一些学习和了解,整理了一些内容,将这些问题解释清楚了,以供参考。
现代编码模型
为了全面地了解字符编码与解码,先学习了解现代编码模型是十分必要的。该模型脱离具体编程语言,了解了它才能了解字符编码的核心。
现代编码模型自底向上分为五个层次:
- 抽象字符表 ACR (Abstract Character Repertoire)
- 编码字符集 CCS (Coded Character Set)
- 字符编码表 CEF (Character Encoding Form)
- 字符编码方案 CES (Character Encoding Schema)
- 传输编码语法 TES (Transfer Encoding Syntax)
抽象字符表ACR
抽象字符表(ACR)也称为抽象字符集,或字符集。表示由若干字符组成的集合,该集合是无序的。这些字符包括英文字母、中文汉字等。更为具体地说,抽象字符 英文字母A
同时属于US-ASCII, UCS, GBK这三个字符集。
抽象字符 中文文字蛤
不属于US-ASCII,属于GBK字符集,也属于UCS字符集。
抽象字符集也是有开放与封闭之分的。开放的字符集指还会不断新增字符的字符集,封闭字符集是指不会新增字符的字符集。
编码字符集CCS
为了更好的描述,操作字符,我们可以为抽象字符集中的每个字符关联一个数字编号,这个数字编号称之为码位(Code Point)。通常根据习惯,我们为字符分配的码位通常都是非负整数,习惯上用十六进制表示。且一个编码字符集中字符与码位的映射是一一映射
编码字符集就是一个每个所属字符都分配了码位的抽象字符集。编码字符集(CCS)也经常简单叫做字符集(Character Set)。
对于同一个字符,使用抽象字符集与编码字符集描述它,有着如下不同:
- ASCII(抽象)字符集中的那个代表什么都没有的通常表示为NULL的抽象字符
- ASCII(编码)字符集中的0号字符
最常见的编码字符集就是统一字符集 UCS,通常说的“Unicode字符集”指的就是它。但是,有时“Unicode”这个词本身指的是一系列用于计算机表示所有语言字符的标准。
字符编码表CEF
已经有了编码字符集,且这个字符集中的每个字符都有一个非负整数码位与之一一对应。是否解决所有问题了呢?答案是否定的。因为UCS是一个开放字符集,未来可能有更多的符号加入到这个字符集中来。也就是说UCS需要的码位,理论上是无限的。但计算机整形能表示的整数范围是有限的。一对有限与无限的矛盾,必须通过一种方式进行调和。这个解决方案,就是字符编码表(Character Encoding Form)。
字符编码表将码位(Code Point)映射为码元序列(Code Unit Sequences)。
- 码元
码元是能用于处理或交换编码文本的最小比特组合。通常计算机处理字符的码元为一字节,即8bit。
在Unicode中,指定了三种标准的字符编码表,UTF-8, UTF-16, UTF-32。分别将Unicode标量值映射为比特数为8、16、32的码元的序列。
需要注意一点的是,CEF将码位映射为码元序列。这个映射必须是一一映射(双射)。
因为当使用CEF进行编码(Encode)时,是将码位映射为码元序列。
而当使用CEF进行解码(Decode)时,是将码元序列还原为码位。
知道了字符编码表CEF是什么还不够,我们还需要知道它是怎么做的。即:如何将一个无限大的整数,一一映射为指定字宽的码元序列。这个问题可以通过变长编码来解决:无论是UTF-8还是UTF-16,本质思想都是通过预留标记位来指示码元序列的长度,从而实现变长编码。
这里的码元序列,是否就是我们编码之后得到的二进制序列?答案是否定的。为了了解它,还需要了解字符编码方案。
字符编码方案
简单说,字符编码方案 CES 等于 字符编码表CEF 加上字节序列化的方案。也就是说,通过CEF得到码元序列,还需要对其字节排序,得到的就是最后的编码结果。
对于一个字符按照UTF16拆成了若干个码元组成的码元序列,因为每个码元都是一个unsigned short,实际上是两个字节。因此将码元序列化为字节序列的时候,就会遇到一些问题。
- 大小端序问题:每个码元究竟是高位字节在前还是低位字节在前呢?
- 字节序标记问题:另一个程序如何知道当文本是什么端序的呢?这些都是CEF需要操心的问题。
字节序标记BOM (Byte Order Mark),则是放置于编码字节序列开始处的一段特殊字节序列,用于表示文本序列的大小端序。
对于这两个问题的不同答案,在3种CEF:UTF-8,UTF-16,UTF-32上。
Unicode实际上定义了 7种 字符编码方案CES:
- UTF-8
- UTF-16LE
- UTF-16BE
- UTF-16
- UTF-32LE
- UTF-32BE
- UTF-32
其中UTF-8因为已经采用字节作为码元了,所以实际上不存在字节序的问题。其他两种CES嘛,都有一个大端版本一个小端版本,还有一个随机应变大小端带BOM的版本。
这里也出现一个问题,历史上字符编码方案(Character Encoding Schema)曾经就是指UTF(Unicode Transformation Formats)。所以UTF-X到底是属于字符编码方案CES还是属于字符编码表CEF是一个模棱两可的问题。UTF-X可以同时指代字符编码表CEF或者字符编码方案CES。
简单的说,字符编码表CEF和字符编码方案CES区别如下:
- 字符编码表CEF将码位映射为码元序列
CCS ---CEF--> Code Unit Sequence - 字符编码方案CES将码位序列化为字节流。
CCS ---CES--> Byte Sequence
总结:
我们通常所说的动词编码(Encode)就是指使用CES,将CCS中字符组成的字符串转变为字节序列。
而解码(Decode)就是反过来,将 编码字节序列 通过CES的一一映射还原为CCS中字符的序列。
对于一些字符集的编码方案太简单,以至于CCS,CEF,CES三层直接合一了。例如US-ASCII的CES,因为ASCII就128个字符,只要直接把其码位转换成(char),就完成了编码。如此简单的编码,直接让CCS,CEF,CES三层合一。很多其他的字符集也与之类似。
传输编码语法(Transfer Encoding Syntax)
通过CES,我们已经可以将一个字符表示为一个字节序列。但是有时候,字节序列表示还不够。比如在HTTP协议中,在URL里,一些字符是不允许出现的。这时候就需要再次对字节流进行编码。著名的Base64编码,就是把字节流映射成了一个由64个安全字符组成字符集所表示的字符流。从而使字节流能够安全地在Web中传输。
Python 3 中的字符编码解码
转换关系
- str ---encode--> byte
- str <--decode--- byte
在str中,没有decode方法,在bytes中,没有encode方法。
>>> u'好'
'好'
>>> '好'
'好'
>>> u'\u597d'
'好'
>>> u'\u597d'.encode('utf-8')
b'\xe5\xa5\xbd'
前缀带u的,和不带u的字符串是等价的,同时,'597d'表示’好‘的CCS字符集’码位‘。对于'\u597d'在Python中似乎与字符串’好‘等价
网友评论