文档章节

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

国栋
 国栋
发布于 2015/05/11 19:46
字数 4031
阅读 6239
收藏 315

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

混乱的现状

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

很多的非文本文件比如图片文件通常会在文件的头部加上所谓的“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”,保存,然后再查看文件的大小属性

image

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

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

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当成了缺省的编码,无论你在什么地区都是如此。这对于减少混乱还是有帮助的。

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

© 著作权归作者所有

国栋

国栋

粉丝 396
博文 79
码字总数 154046
作品 0
东莞
程序员
私信 提问
加载中

评论(35)

姚文强
姚文强
好文
ZHAOXJMAIL
ZHAOXJMAIL
好文章!
我能帮您什么忙吗
我能帮您什么忙吗
nice mark
我能帮您什么忙吗
我能帮您什么忙吗
nice mark
lookfuyao
lookfuyao
Mark
国栋
国栋 博主

引用来自“莫扎特的洗礼”的评论

引用来自“开源中国七里香”的评论

mark下慢慢看,我想知道,你的Notepad++怎么显示十六进制信息的

同问
见19楼
JadenTseng
JadenTseng

引用来自“开源中国七里香”的评论

mark下慢慢看,我想知道,你的Notepad++怎么显示十六进制信息的

同问
吴中勤
吴中勤
刘明
暴怒的栗子
暴怒的栗子
mark
4
iman123
iman123
我前段时间也写了一个桌面小应用用来自动检测文件编码,然后可以批量编码转换的,还不完善,后来各种事情忙就没有继续完善了,如果感兴趣可以给我pr!
目前使用JavaFx做界面,universal chardet检测编码,iconv编码格式转换!
但universal chardet支持检测的UTF8是需要带bom,而iconv转换的UTF8是没有bom的

项目地址
https://github.com/liudonghua123/iConvert
文件,文本文件以及编码——乱码探源(1)

在前面的字符集编码系列中,已经探讨了几大主要的字符集编码。在此基础之上,这里将进一步探讨编码的应用及乱码的根源,我们先从基本的文件说起。 文件 文件(内容)就是字节序列。文本文件也...

国栋
2015/05/05
1K
0
文本在内存中的编码(2)——乱码探源(5)

在前面我们探讨了String是什么的问题,现在来看String从哪来的问题。 String从哪里来? 所谓从哪里来也可以看作是String的构造问题,因此我们会从String的构造函数说起。 String的构造函数 ...

国栋
2015/06/26
2.1K
2
cmd命令行中logcat输出日志中文乱码

http://www.6san.com/913/ 在命令行使用adb logcat命令直接输出日志中文内容显示乱码,原因是中文系统中cmd命令行窗口默认的编码是GBK,而LogCat打印的日志是UTF-8编码,所以adb logcat命令输...

塔塔米
2014/01/30
1K
0
Beyond Compare乱码问题这样就能解决

如果你每天都有大量的文件需要进行比较,那么你一定需要一款比较神器来助你摆脱繁杂的工作,Beyond Compare 中文版就是这样一款专业的文件和文件夹比较工具。如果你以为找到这款对比神器就万...

Navicat数据库管理工具
2016/07/07
157
0
文本在内存中的编码(3)——乱码探源(6)

先讲个小故事,虽然跟主题有点不太相关哈: 唐朝诗人李绅,身为官员,脾气暴躁,瞧不起信教的,尤其鄙视装逼之僧人,动不动就对他们拳脚相加。曾扬言:“我可以接见他们,要能答出来还好,要...

国栋
2015/06/26
615
5

没有更多内容

加载失败,请刷新页面

加载更多

Android 图片加载带进度条的ImageView

https://blog.csdn.net/shu_quan/article/details/79975578

shzwork
23分钟前
7
0
关于XAMPP默认端口80 和443被占用的问题

本文转载于:专业的前端网站➩关于XAMPP默认端口80 和443被占用的问题 关于安装xampp-win32-1.8.1-VC9-installer.zip后启动时候报端口80和443被占用的问题解决 xampp-win32-1.8.1-VC9-instal...

前端老手
25分钟前
6
0
错误Setting the parent of a transform which resides in a Prefab Asset is...

错误日志 Setting the parent of a transform which resides in a Prefab Asset is disabled to prevent data corruption 原因1 用Resouce.Load加载一个prefab,没有实例化直接设置parent ......

XBlock
26分钟前
9
0
Spring boot 配置mybatis

当然任何模式都需要首先引入mybatis-spring-boot-starter的pom文件,现在最新版本是1.1.1(刚好快到双11了 :)) <dependency>    <groupId>org.mybatis.spring.boot</groupId>    <......

雷开你的门
27分钟前
9
0
云栖干货回顾 | 更强大的实时数仓构建能力!分析型数据库PostgreSQL 6.0新特性解读

阿里云 AnalyticDB for PostgreSQL 为采用MPP架构的分布式集群数据库,完备支持SQL 2003,部分兼容Oracle语法,支持PL/SQL存储过程,触发器,支持标准数据库事务ACID。AnalyticDB PG通过行存...

开源中国小二
39分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部