在前面的字符集编码系列中,已经探讨了几大主要的字符集编码。在此基础之上,这里将进一步探讨编码的应用及乱码的根源,我们先从基本的文件说起。
文件(内容)就是字节序列。文本文件也是文件,所以它也是字节序列。
通常说到文件时,指的是文件内容,但文件还有文件名,文件名与文件内容是分开存储的。
你可以在硬盘上新建一个文件,它的大小为0.如下:
但它是有文件名的,比如上述的“新建文本文档.txt“,保存这些名字自然也要占用空间,只不过它与文件内容是分离的。
这些由操作系统的文件系统模块负责。
文件名是一段文本,因此它会涉及字符集编码。
文件内容则视情况而定:
1. 文本文件,肯定会涉及字符集编码。
常见的比如txt,html,xml以及各种源代码文件等等。
2. 非文本文件,比如图片文件jpg,gif之类,自然跟字符集编码无关了。
有些文件,比如Word的doc之类的,混合了图片跟文本在里面,可以想像,其中的文本部分自然也会牵涉到字符集编码的问题,只不过这些编码不由我们去控制,我们通常也无须去关心。
文件名是一串文本,因此它必然涉及某种字符集编码,只不过这种编码是由操作系统决定的,我们无权干预。
那么,它用的是什么编码呢?在Windows下,可以简单做些实验。我们可以弄些奇怪的文件名如“★★★★.txt”,如下:
结果也能保存。这些字符只在Unicode中才有,所以它肯定不是用的GBK之类的。
Windows下NTFS架构文件名使用UTF-16编码。但对于FAT之类的,则是所谓的“OEM character set”。
MSDN上的原文如下:“NTFS stores file names in Unicode. In contrast, the older FAT12, FAT16, and FAT32 file systems use the OEM character set”(NTFS使用Unicode存储文件名。与此相对,老的FAT12,FAT16和FAT32文件系统使用OEM字符集).参见http://msdn.microsoft.com/en-us/library/windows/desktop/dd317748%28v=vs.85%29.aspx
注:在Windows语境中,UTF-16通常叫成Unicode。
结合实验的结果,可以确定,Windows使用UTF-16对文件名进行编码。(我的系统是Win 7,文件系统为NTFS)
不过,不同的系统平台可能使用了不同的编码。比如最新的Linux平台对文件名采用了UTF-8编码,但早期的则不好说,甚至没有一个标准。
如果你不是Windows平台,你也可以简单做些实验来大致猜测一下文件系统使用的编码。
由于对文件名没有一个统一的编码,不同系统平台间交换文件时,中文文件名极易发生乱码现象。比如FTP上传,网页文件上传及下载等情况下经常能遇到文件名乱码。
不过,需要注意的是,交换过程中,文件内容不会发生任何改变。即便是文本文件,也完全是字节传送,不会涉及任何的编解码。
你可能碰到过这样的事,把一个文本文件从Windows平台上传到Linux平台,并在Linux平台下打开时发现乱码了,但这不意味着文件内容有了什么变化,通常的原因是你的文件是用GBK编码的,但Linux平台下打开时它缺省可能用的是UTF-8编码去读取,因此,你只要调整成正确的编码去读取即可。
在这里,我们讨论了文件名的编码,之后,如无特别说明,谈到编码时均指对文件内容的编码。通常,这是我们更为关心的内容。
通常,说到字符集编码都是对文本文件而言的,但非文本文件也是可能用到字符集编码的。
比如,Word用什么编码?word生成的doc或者docx虽然不是文本文件,但我们可以想像,它里面可能有图像,又有文字。其中的文字自然也会用到某种编码。只不过,这些都不需要我们去操心。
下面是一个实验,新建一个空白的doc文档,录入几个简单字符”Hello你好”
保存成doc文件,再用notepad++打开,以十六进制形式查看:
如上图,搜索到hello几个关键字,我们知道,“H”的码点是U+0048,而“你”的码点则是U+4F60,所以,很显然,用的是UTF-16 LE(Little Endian,小端序)
关于端序及BOM的相关话题,可参见字符集与编码(七)——BOM
注:这只是我个人在本机测试的结果,不代表普遍的结论,不同平台不同版本下的可能会有差异,谁知道呢?我没有去研究过doc文件格式的规范,这个doc我还是用WPS生成的!
又比如,Java中的class文件,它也不是文本文件,通常称为字节码文件。但它里面也会保存String的常量,这自然又要牵涉到编码。实际用的是所谓的“modified UTF-8”编码。
简单建立一个java文件,定义一个string常量”Hello你好“:
public class Foo { static final String HI = "Hello你好"; }
保存并用javac命令编译得到class文件,再次用notepad++打开并以十六进制形式查看:
搜索到Hello几个关键字,紧接在它们后面的”e4 bd a0“就是”你“的UTF-8编码了。
在前面的字符集与编码(四)——Unicode中,曾提到过,汉字的UTF-8编码通常都是以e打头,形如ex xx xx这样,这是常用汉字UTF-8编码的一个重要特征。
这个”modified UTF-8”编码与UTF-8类似,但有一些差别,它的名字也暗示了这一点。
比如对于U+0000它用了两字节来编码;
还有对U+FFFF以上的字符它采用了6字节编码而非正常UTF-8的四字节编码,实质是对代理对(surrogate pairs)的值进行编码。
详情可参见http://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#modified-utf-8
文本文件也是文件,所以它也是字节序列。当读取一个文本文件时,最重要的是确定它所使用的编码,只有这样才能正确的解码。
由于牵涉的情况较多,我们将在下一篇讨论这一问题。