长期以来,一直对字符串编码认识比较粗略,认为支持"特殊字符"编码就是Unicode。当然,.NET平台上很少需要考虑这类问题,但搞清一些基本概念还是很有好处的。
Unicode这个词,首先是国际标准的通用字符集(UCS)名称,囊括了汉语八国联军火星文等各种文字。这是一个面向用户的字符编码标准。其他的编码标准如GB2132,BIG5什么的都是Unicode标准之前的老黄历了,彼此间,与现代系统间各种不兼容。
而.Net中的UnicodeEncoding类,是实现Unicode字符集的一种编码方式,将一个字符转换成字节形式。其名称容易引起qi义,其实这个编码方式通用名称(在其他编程语言中)叫UTF16,此外流行的两种还有UTF8和UTF32,UTF8更常用。UTF是UCS Transformation Format的缩写。
这里也不贴具体的转换方式了,有兴趣可以到百科上去看。关键要知道.NET中UnicodeEncoding和UTF8Encoding两个类主要区别,前者每个字符一律都是两个字节,后者可能是1~3个字节:英文字母和标点1个,汉字占3个。显然这两种方式也不是兼容的,各有长短。
然而我们打开绝大多数文本文件,不管哪种编码,都可以正常显示所有内容。那么系统是如何判断文件的编码呢?我们可以做一个测试。
var filename = "encoding-test"; var text = "变!"; var encodingInFile = Encoding.Unicode; var encodingToCompare = Encoding.UTF8; //UTF16 encoding Console.WriteLine("Saved with UTF16 Encoding:"); File.WriteAllText(filename, text, encodingInFile); Console.WriteLine("Read without Encoding: {0}", File.ReadAllText(filename)); Console.WriteLine("Read with Encoding of File: {0}", File.ReadAllText(filename, encodingInFile)); Console.WriteLine("Read with Another Encoding: {0}", File.ReadAllText(filename, encodingToCompare)); Console.WriteLine();
可看到,无论是否指定,指定了何种编码去读取文本文件,都不影响系统识别正确的编码,输出结果都是"变!"。写入文件的Encoding换成UTF8等也一样,只要支持Unicode就可以。
但我们从字节上分析,就可以发现其中的区别,接着上面的代码,测试如下:
var bytes = encodingInFile.GetBytes(text); Console.WriteLine("Bytes from Text: {0}", String.Join(" ", bytes.Select(b => b.ToString("X")))); Console.WriteLine("Text from Bytes with File Encoding: {0}", encodingInFile.GetString(bytes)); Console.WriteLine("Text from Bytes with Another Encoding: {0}", encodingToCompare.GetString(bytes)); Console.WriteLine(); bytes = File.ReadAllBytes(filename); Console.WriteLine("Bytes from File: {0}", String.Join(" ", bytes.Select(b => b.ToString("X")))); Console.WriteLine("Text from Bytes with File Encoding: {0}", encodingInFile.GetString(bytes)); Console.WriteLine("Text from Bytes with Another Encoding: {0}", encodingToCompare.GetString(bytes));
输出结果将是:
Bytes from Text: D8 53 21 0
Text from Bytes with File Encoding: 变!
Text from Bytes with Another Encoding: ?S!
Bytes from File: FF FE D8 53 21 0
Text from Bytes with File Encoding: ?变!
Text from Bytes with Another Encoding: ???S!
我们可以看到两点,一是字节数组转化字符串必须指定正确的Encoding;二是在文本文件中,系统在写入时多加了两个字节 FF FE,这就是系统能识别文件编码的关键。
这多余的两个字节,就是Unicode标准建议的用BOM(Byte Order Mark)。在写入传输表示文本的字节,先传输被作为BOM的字节以表明编码。对于UTF16或是UnicodeEncoding类,是FF FFE;对于UTF8则是EF BB BF,可以自己试一下。
所以在一般情况下,读取文本文件根本不必指定Encoding,必须指定Encoding是处理异常情况:该文件没有正确的保存,比如直接写入字节而不是文本,可能还有不少软件或系统会这么干,这样很容易造成读写错误而出现乱码。
要指出Excel就是这种我行我素的程序,对于CSV文件,要是用户想编辑输入Unicode字符,它会强迫你转换成xlsx格式,不然它连UTF8格式字节都不给你写入,用户重新打开后发现上次输入的Unicode字符全变成了问号。
另一个最常见的乱码原因是历史造成的。Unicode出现前,中韩日各种字符集百花乱放。这些旧字符集编码的文件,以及用了这种编码的程序,任何Unicode方式解码出来都是乱码,就像用密码加密一样。微软为解决这个问题,采取了页转换表作为过渡技术。在控制面板->区域语言选项->管理,可以找到“非Unicode程序中所使用的当前语言”,就是指定就种页表的。可以从Encoding.GetEncoding(int)取到,参数936是简体中文GBK,950是繁体中文BIG5。然而只能指定了一种非Unicode编码,如果还有电脑上还有别的非Unicode编码,那就只能任乱码横行了。
根本的解决之道,就是标准化,就是大家都用Unicode。至于选UTF8还是UTF16,看情况,对于中文文档,UTF16可以每个字节省一个字节。而国际上通用UTF8,也是XML标准编码。