肯定文本文件的编码——乱码探源(2)

在上一篇中,探讨了文件名编码以及非文本文件中的文本内容的编码,在这里,将介绍更为重要的文本文件的编码。程序员

混乱的现状

设想一下,若是在保存文本文件时,也同时把所使用的编码的信息也保存在文件内容里,那么,在再次读取时,肯定所使用的编码就容易多了。eclipse

不少的非文本文件好比图片文件一般会在文件的头部加上所谓的“magic number(魔法数字)”来做为一种标识。所谓的“magic number”,其实它就是一个或几个固定的字节构成的固定值,用于标识文件的种类(相似于签名)。好比bmp文件一般会以“42 4D”两字节开头。工具

又好比Java的class文件,则是以四字节的“ca fe ba be”打头。(咖啡宝贝?)字体

image

即使没有文件后缀名,根据这些信息也是肯定一个文件类型的手段。大数据

附:关于用Notepad++查看十六进制的问题,这是一个插件,若是没有装,菜单--插件--plugin manager--available--HEX-Editor,装上它。装上后,它一般在工具栏的最右边,一个黑色的大写的斜体的“H”就是它。单击它能够在正常文本与16进制间切换。要进一步查看二进制,在文本区,右键--view in--to binary。编码

没有编码信息

那么,对于文本文件,有没有这样的好事呢?能够简单创建一个文本文件“foo.txt”,里面输入两个简单的字符,好比“hi”,保存,而后再查看文件的大小属性spa

image

而后,咱们很遗憾地发现,大小只有2,也即“hi”两个字符的大小,这意味着没有保存额外的所用编码的信息。操作系统

用十六进制形式查看,也能够发现这两个字节就是hi两字符的编码:.net

image

关于字母的ASCII编码,可查看字符集与编码(八)——ASCII和ISO-8859-1插件

那么,如今很清楚了,文本文件仅仅是内容的字节序列,没有其它额外的信息。

BOM?

固然,说绝对没有额外信息也不彻底正确,在以前的关于BOM的介绍中,咱们看到BOM其实能够当作是一种额外的信息。

参见字符集与编码(七)——BOM

保持内容不变,简单地“另存为”一下,在编码一栏选择“UTF-8”,再次查看属性将会发现大小变成了5.

image

再次查看十六进制形式时,就会发现除了原来的“68 69”外,还多出了UTF-8的BOM:“ef bb bf”

image

也正是以上三个与内容无关的字节使得大小变成了5.

这个信息是与所用编码有关的,不过它仅能肯定与Unicode相关的编码。

严格地说,BOM的目的是用于肯定字节序的。

另外一方面,对于UTF-8而言,如今一般不建议使用BOM,由于UTF-8的字节序是固定的,因此不少的UTF-8编码的文本文件实际上是没有BOM的,不能简单地认为没有BOM就不是UTF-8了。

好比eclipse中生成的UTF-8文件默认就是不带BOM的。微软的笔记本应该是比较特殊的状况。

综上所述,文本文件一般没有一个特殊的头部信息来供肯定所用的编码,另外一方面,编码的种类又是五花八门,那么如何去肯定编码呢?

肯定编码的步骤

不妨就以记事本为考察对象,去探究一下它是如何肯定编码的。

利用BOM

前面说了,BOM做为一种额外的信息,间接地代表了所使用的编码。尽管它本来的意图是要指明字节序,但曲线救国一下也未必不可。何况记事本还主动地为UTF-8也写入了BOM,不加以利用这一信息天然是不明智的。

注:对UTF-16来讲,BOM是必须的,由于它是存在字节序的,弄反了字节序一个编码就会变成另外一个编码了,那就完全乱套了。不过通常不多用UTF-16编码来保存文件的,更可能是在内存中使用它做为一种统一的编码。

但对于UTF-8,不少时候也是没有BOM的,记事本遇到UTF-8 without BOM时又该怎么办呢?

我猜,我猜,我猜猜猜

若是内容中没有编码信息,又要去肯定它使用的编码,这不是为难人是什么?好在“坑蒙拐骗”中的第二招“蒙”能够拿来用用。

“蒙”其实也是要讲点技术含量的,简单点天然就是是模式匹配了,或许一个或几个正则式就完了;复杂点,什么几率论,统计学,大数据通通给它弄上去,那逼格立马就高了有木有?固然了,记事本也就是一跑龙套的...

记事本跟“联通”有仇?

在编码界有这么一个传说:记事本跟“联通”有仇。这是怎么一回事呢?

新建一个文本文件“test.txt”,录入两个汉字“联通”,保存,关闭程序而后再次打开这一文件:

image

咦,这是什么鬼?我们的“联通”呢?

深刻分析

这其实就是竞猜失败的结果了,准确地讲,记事本把编码给猜成了UTF-8.

为何说是猜成UTF-8形成的呢?我也没见过源码!接下来会根据出现的现象,已有的证据来做出咱们的推论,而后还会作些实验去验证。(没错,这就是科学!)

首先是这样一个事实:当咱们保存时,使用的是缺省编码,也就是GBK。

“联通”两字的GBK编码以下:

image

而后,是这样一个现象:再次打开时,记事本忽然就翻脸不认人了,显示出了一些奇怪的字符。

严格地讲,有三个字符,两个问号及一个C同样的字符,后面会分析为什么会这样。

以上字节咋一看也没啥子特别的,领头字节也没有刚好等于UTF-16或UTF-8的BOM,绝对标准的GBK模式,为啥记事本对它刮目相看了呢?

关于GBK等编码,可参见字符集与编码(九)——GB2312,GBK,GB18030

那么,一个合理的猜想就是记事本可能把它当成了无BOM的UTF-8编码。

而对于UTF-8编码来讲,它的编码模式仍是颇有本身特点的,那么咱们换成二进制形式查看以上编码:

image

看到以上编码,相信对UTF-8编码模式有一些了解的都知道是怎么回事了,我也用颜色的下标标注了关键的部分。

在前面的篇章中,也曾经几回说到过UTF-8的编码模式:

image

字符集与编码(三)——定长与变长字符集与编码(四)——Unicode

对比一下,不难发现上述两个编码彻底符合二字节模式!也难怪记事本犯迷糊了。

天然,要作出一些“科学”的发现,你仍是须要必定的基础的。因此在这里也给出了不少前面文章的连接。

显示疑云

那么,为什么又显示成了那样的效果呢?

既然推断它是UTF-8编码,让咱们人肉解码一下:

前两字节构成一组:11000001 10101010

有效的码位有三段:11000001 10101010

重组成码点以后是:00000000 01101010

以上码点写成16进制是U+006A,这明明是一个一字节的码点!对应的字母实际上是小写字母“j”。

因此,这里实际上是有错误的。这个码点不该该用二字节来编码。

若是你读过前面关于Unicode的篇章,就会明白,对于UTF-8编码而言,码点在U+0000~U+007F(0-127)间的用一字节模式编码。码点在U+0080~U+07FF(128-2047)间的才用二字节模式编码。

别问为何!这叫乌龟的屁股——龟腚(规定)。

理论上讲,二字节的空间是彻底能够囊括一字节编码的那些码点的,各类模式间实际上是有重叠与冗余的。但若是一个码点适用于更少字节,那么它应该优先用更少字节的编码模式。

因此,一般说UTF-8的二字节模式是“110x xxxx, 10xx xxxx”,但并不是全部知足这些模式的编码都是合法的UTF-8二字节编码。这里面实际上是有个坑的。二字节首个码点为U+0080,对应二字节模式首字节为“1100 0010”,那么,全部合法的二字节模式首字节不该该小于此值。

显然,亲爱的微软的写记事本的程序员们,大家偷懒了!大家至少应该能够避免与“联通”结仇。

那么,虽然可以解出相应的码点,但实际上是非法的组合,这也就是结果显示出“�”的缘由。这一般是一个明显的解释失败的标志。

注:“�”自己是一个合法的Unicode字符,码点为U+FFFD,对应的UTF-8编码为:EF BF BD。若是字体不支持的话,可参见http://www.fileformat.info/info/unicode/char/fffd/index.htm

image

这可不是“显示不出来”形成的,它自己就长这样。

这个码点的含义为“replacement character”(替换字符)。当碰到非法的字节时,显示系统就用它来替换,而后显示这个替换字符来表示发生了替换。因此,那些真正“显示不出来”的东西已经被替换了。(若是文件中自己就包含这个字符的话,替换上去的和原有的实际是没法区分的。)

在这里,程序实际临时在内存中作了替换,若是你对它进行拷贝,获得的也将是它的值,而不是原来的值。

因此,显示层面出现了问号(包括早期的ASCII中那个问号“?”,U+001A,也经常使用做替换字符),不表明它不清楚如何显示,而彻底是由于最终交给它去渲染的就是“问号”字符。(多是替换上去的,也可能自己就有的。)

在显示层面,原来的值实际已经丢失了。

至于为什么显示了两个问号,大概是把两个字节看成了两次失败。我的认为显示一个问号也能说得通。

再来看第二组

后两字节构成一组:11001101 10101000

有效的码位有三段:11001101 10101000

重组成码点以后是:00000011 01101000

以上码点写成16进制是U+0368,对应的字母实际上是所谓的COMBINING LATIN SMALL LETTER Cͨ(<--这里第二个C就是U+0368)。见“http://www.fileformat.info/info/unicode/char/0368/index.htm

显示时,它会紧贴在前面的字母上,这是Unicode中我的感受比较奇葩的一些内容,若是你有兴趣,这是wiki的一些介绍http://en.wikipedia.org/wiki/Combining_character

前面乱码的截图中,那个怪怪的C就是这样来的,感受好像与前面一个问号是一体的同样。

image

“联通”这一案例还真多梗。

至此,冤有头,债有主,一切都水落石出。最终咱们的猜想被证明。

直接证据!

其实,在变成怪怪的字符后,若是咱们点击“另存为”,在弹出的对话框中会发现编码成了“UTF-8”

image

这是赤裸裸的证据,直接代表了记事本把编码误判成了UTF-8!简直是铁证如山呀!

打破模式

删掉test.txt,重新再创建,此次把联通的好基友“电信”也一块儿录入,录入“联通电信”四个字,保存并再次打开时记事本就再也不抽疯了,由于“电信”两字的GBK编码模式与UTF-8不能匹配了,读者可自行验证一下,这里就再也不贴图展现了。

注:不要直接删除原来的乱码字符并从新录入,前面“另存为”已经代表它已经成了UTF-8编码,直接在原文件修改将致使以UTF-8编码保存。因此应该删除原文件。

有句话叫“无巧不成书”,记事本跟“联通”有仇,这其实就是一个由于样本太少而误判的典型例子。

缺省编码,ANSI是个什么玩意

若是既没有BOM,又没法猜想出所使用的编码,那是否就只能是两眼一抹黑了呢?

还好,计算机世界还有件贴心的小棉袄叫“缺省”。

其实,当你保存任何一个文本文件时,指定一个编码是必不可少的一个步骤。

与此相似,读取一个文本文件时,或者说是好比Java中new一个Reader字符流时,又或者是string.getBytes时,你其实都是须要指定一个编码的。

但不少时候,咱们并无感受到须要这一步骤,缘由就是“缺省”在为咱们默默地服务。

缺省这玩意,怎么说它好呢?当它正常时,你好,我好,你们好。当它不正常时,你甚至不知道哪儿出错了。你过于依赖它,它极可能成为你的定时炸弹。不得不说,不少时候咱们实际上是抱着炸弹在击鼓传花,还玩得不亦乐乎,直到“轰”的一声,咦?头上何时多了个圈?

以记事本为例,当咱们新建一个文件并保存时,实际上是有个选项的,一般,这里会缺省地选上“ANSI”

image_thumb3

那么这里的ANSI又是个什么鬼呢?

ANSI(American National Standards Institute美国国家标准学会)与它字面的意思并不相符,它也不是一种真正意义上的编码。

一般把它理解成平台缺省编码,它具体指代什么则一般与平台所在地区的Windows发行版本有关。

像咱们这些火墙内的大陆人民,多数人用的Windows版本,ANSI指的是GBK;在香港台湾地区,它多是Big5;在一些欧洲地区,它则多是ISO-8859-1。

除了ANSI以外,在这里还有其它的选项。

其实在这里短短的一个下拉列表,到处都是坑呀,说多了都是泪。

1. ANSI,前面已说,就不说了。

2. Unicode。实际上是UTF-16,具体地讲是UTF-16 little endian(UTF-16 LE)。这个缺省为Little Endian也仅是微软平台的缺省。其它平台未必是如此。

3. Unicode big endian。与以前相似,就是UTF-16 big endian(UTF-16 BE)。Unicode如今的含义太宽泛,能够指Unicode字符集,能够指Unicode码点,也能够指整个Unicode标准。如今看来,把UTF-16继续叫成Unicode实在是很坑爹,除了容易引起误解,我还真没想到它还能有什么其它好处~

4. UTF-8.实际上是“带BOM的UTF-8”,而真正推荐的缺省作法是“不带BOM”。微软就是任性!

还须要注意的是,不一样的操做系统对于缺省有不一样的策略。

好比如今不少的Linux的操做系统都把UTF-8当成了缺省的编码,不管你在什么地区都是如此。这对于减小混乱仍是有帮助的。

由于文件内容没有编码的信息,各个系统平台对于缺省的规定又各不相同,种种状况致使了乱码问题层出不穷,下一篇,将探讨引入编码信息的一些实践。

相关文章
相关标签/搜索