字符集和编码 I:formalization

按:原文主要关于fat/vfat,于07.6.15写在未名上。另外之前也写过关于cifs字符集的问题,有回复说选项有问题,于是就研究了下我当时为什么那么设字符集。研究的结果是,用映射的概念来解释字符集的问题无比有力及方便~~所以改了下未名的文章,再加上cifs的内容。

随着UNICODE和UTF-8的普及,字符集和编码不应该再成为问题;不过由于各种原因(兼容性和历史遗留),还是造成了很多困扰。

写这个问题的文章很多,多得泛滥,不过我觉得按映射的术语来解释很好玩,而且所有的概念都不会含混不清,所以重复一下也挺有意义的。

  1. 给定我们能用到(或者想到)的符号集\(\mathcal{U}\),它包括了所有可能的各种语言和符号——这也是我们人类所能理解的符号。
  2. 字符集(Charset):一个字符集定义为\[\mathcal{C}= \left\{ \left( u, n \right) |u \in \mathcal{U}, n \in \mathbb{N} \right\},\]它可以看作是\(\mathcal{U}\)的一个有序子集(也就是给每个字符指定序号),如ISO8859-1(即ANSI,包括最基本的拉丁字母)、GB2312(包括一部分简体汉字)。字符集仍然是面向人类的。

    一个特殊的字符集是Unicode,由于它的目标是包括所有的字符并且事实上包含了所有现存的字符集,因此可以把Unicode等同于\(\mathcal{U}\)。从这里开始我们不再区分符号集\(\mathcal{U}\)和Unicode字符集。

  3. 编码(Encoding):字符集\(\mathcal{C}\)上的编码是一个如下的映射\[f_{enc} : \mathcal{C} \mapsto \mathcal{T}_{enc},\]其中\(\mathcal{T}_{enc}\)是可存储的字节流。编码结果可以是定长字节的,如UCS-2(双字节)、ANSI(单字节);也可以是不定长字节的,如UTF-8。显然,编码是面向机器的。

尽管还有可以推敲之处,但以上的框架基本解释清楚了字符集、编码,特别是说明了UNICODE、UCS-2、UTF-8之间的关系,帮助我们厘清各种概念。比如,GBK是字符集,同时也是它自身的编码;UNICODE是字符集;UCS-2、UTF-8是UNICODE上的编码;等等等等。

以上是对单个字符的处理,但无疑字符串是更有意义的对象。正是字符串给问题带来了更大的复杂性。

  1. 字符串及其编码:给定字符集\(\mathcal{C}\)上长度为\({n}\)的字符串\[s=c_1 c_2 \cdots c_n,\]它在编码enc下的像(也就是最终在计算机上的存储)为\[\varphi_{enc} \left( s \right) = f_{enc} \left( c_1 \right) f_{enc} \left( c_2 \right) \cdots f_{enc} \left( c_n \right).\]若enc是固定字节编码(如ANSI、UCS-2、UCS-4),则\(\varphi_{enc} \left( s \right)\)长度为\(k \cdot n\)个字节,其中\(k\)是每字符的字节数;若enc是不定长编码(UTF-8和大部分CJK编码——只要是包含ANSI的多字节码必然是变长码),则\(\varphi_{enc} \left( s \right)\)长度不定。
  2. 字符串编码映射的逆\(\varphi_{enc}^{-1}\):给定一个字节流\(\varphi_{enc} \left( s \right)\),我们必须给出它唯一的分割使得\[\varphi_{enc} \left( s \right) = f_{enc} \left( c_1 \right) f_{enc} \left( c_2 \right) \cdots f_{enc} \left( c_n \right).\]在固定字节编码中,这一分割是平凡的,将每\(k\)个字节作为一份即可;而不定长编码中,必须采用一些技术使得这个分割存在且唯一。目前大部分都采用的是前缀码,以UTF-8为典型代表。这样,对分割结果再应用\(f_{enc}^{- 1}\),就得到了原字符串\(s\)。

非定长码必须逐字节扫描,因此习惯将它们归于单字节编码;而定长码只需要逐\(k\)字节扫描,其中的UCS-2则是最常用的多字节编码。正是在这个意义(串行扫描)上,UNICODE常常被等同于UCS-2(而在数学或者理论的意义上,UTF-8、UTF-16、UCS-2、UCS-4都与UNICODE等价)。

这样,给定一个串\(s\),假定它以某种编码\(\varphi_{enc} \left( s \right)\)存储在某个地方,我们如果想要看到并看懂\(s\)的内容,逆变换\(\varphi_{enc}^{- 1}\)是必须的。在所有的情况下,只要搞清楚哪个是变换哪个是逆变换,编码都不会再是个问题。

发表评论?

0 条评论。

发表评论


注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">

Switch to our mobile site