文本文件编码的问题

文本文件编码虽然有很多种,但从大类上可分为两大类,ANSI编码和UNICODE编码。这两大类编码方式的主要区别是支持的字符集不一样。前者一般只支持特定语言的特定字符集,后者则是大一统的字符集(理论上覆盖地球上所有用到的字符,以后扩展到外星语也未可知)。

1. ANSI编码: ASCII, GBK

ANSI编码只支持特定语言的特定字符集。最开始的编码只针对一个字节,共有0到255一共256个不同的字节状态:

  • 所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的 文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的"Ascii"编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。
  • 世界各地的都开始使用计算机,但是很多国家用的不是英文,他们用到的许多字母在ASCII中根本没有,为了也可以在计算机中保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称"扩展字符集"。

后来越来越多的语言用到计算机,尤其是中文,远远不止256个汉字,因此中国发展了一条独立的ASCI编码线:

  • 把那些127号之后的奇异符号们直接取消掉,并且规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符。这便是GB2312编码。
  • 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。
  • 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。

我们看到ASCII-GB2312-GBK-GB18030是向下兼容的编码线,其字符集为 "DBCS"(Double Byte Charecter Set 双字节字符集)。这个编码中,英文为一个字符,编码总是小于128,中文为两个字符,第一个字符的编码总是大于127,因此可以自动识别。

不同语言都定义了自己的ANSI编码,比如繁体中文是BIG5。

2. UNICODE编码和UTF-8

上面的ANSI编码可以处理单语言的问题。但遇到多种语言时,问题就来了。不同的ANSI编码,一个字节状态可能对应不同的字符(除了公共的ASCII部分)。从而无法显示不同语言的字符。此时的解决方案是定义一个大一统的字符集,包括地球上所有语言的字符,再对这个字符集定义编码方式。这个字符集就叫做UNICODE,这个编码方式是UTF-8(其实还有很多别的UNICODE编码方式,比如UTF-16,UTF-32等,但目前最常用的还是UTF-8)。

Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。目前UNICODE已经定了超过10万个字符,并且还在继续增加中。其中7万多个字符是汉字。

UTF-8则是最常用的UNICODE编码方式。它是不定长编码,并且兼容ASCII编码,因此英文还是一个字节,但中文要用三个字节来表示。所以,用UTF-8编码的中文文档要比GBK编码的文件大一半左右。

UTF-8的编码和UNICODE编码转换关系如下:

Unicode编码(十六进制)  UTF-8 字节流(二进制)
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

该转换关系没啥用处。因为从UNICODE编码到其它的GBK编码没有简单的转换关系,只能查表实现。不过,这个转换关系可以用来快速计算一个UTF-8编码的字符串中含有几个字符。

3. 编码问题为什么麻烦

编码本身并不复杂,说清楚了就那么几种。麻烦之处不在编码本身的复杂,而在于文件所用的编码方式不是明文规定,而是靠使用方去“猜”,猜错是常有之事。

这个猜也没有特别好的方法,只能一个一个编码方式的尝试。因为不同编码方式用到的码位不一样,如果发现无法识别的码位,便能否认这个编码。但也可能有意外,比如下面这个例子:

当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。

其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。

当你新建一个文本文件时,记事本的编码默认是ANSI(代表系统默认编码,在中文系统中一般是GB系列编码), 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是:

c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000

注意到了吗?第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,

于是当我们再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",不好意思,这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。

而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。

另外某些软件不支持特定的编码,比如微软的VS2013便不支持不带签名的UTF-8编码,这让编码问题的兼容性问题尤其突出。

Copyright © zhiqiang.org 2016 all right reserved,powered by Gitbook该文件修订时间: 2016-08-03 01:06:06

results matching ""

    No results matching ""