_(:з」∠)_连我自己都觉得这个标题略长了……
事情的起因是这样的:
数据管理系统可以自动导出编码为UTF-8的csv格式文件,然而,距离在Windows上直接用Excel打开它却还有一步之遥:乱码。当欢欢喜喜地直接打开这个文件时,发现其中的汉字部分几乎都是???,这个对于含有汉字的文件肯定是不可接受的。
查询了一下之后发现,这是由于微软会悄悄地在UTF-8编码的文件前面添加BOM头,所谓BOM头是在文件最开头的16进制的0xEF 0xBB 0xBF这三位。
反过来看的话,也就是说,如果UTF-8编码的文件没有BOM头的话,Windows下的软件就可能无法识别UTF-8格式,采用其他的读取办法,从而就造成了乱码。Excel就是其中一个。
在理解了原理之后,我马上就着手开始做对文件的转码。想当然地,借用了相当流行(目前中文的技术博客互相转载过多,有时真是让搜索的人觉得有点沮丧)的16进制和字符串互转的方法,读取文件则采用BufferedReader(FileReader)直接使用readLine()的方法。总的来说,就是第一行转16进制->插入BOM头->16进制转第一行->依次读取余行不做操作写入文件——完成啦!
……这么想的我真是太天真了!
在完成了一段小代码,打成jar包并测试结果之后,发现一些汉字确实成功地转码了,而另外一部分则显示得很微妙。举个例子来说,其中“家”显示成了“宿”,“区”显示成了“匿”。
在初次尝试之后,推测还是编码上有一些问题。
Javadoc上对于FileReader/FileWriter类的说明是,它们分别是InputStreamReader/OutputStreamReader的拓展类,它们会使用【默认的编码】,如果需要指定编码的种类的话,则需要使用InputStreamReader/OutputStreamReader。
沿着这个线索,我尝试在NetBeans8.0和jar包在系统运行这两种情况下,打印了FileReader/FileWriter的运行的编码,结果编码为:
(1) NetBeans UTF-8
(2) Windows GBK
结合stackflow上的解答,可以推断出,FileReader/FileWriter读取和写入的编码,是与运行平台的默认编码一致的。[1]
参考了InputStreamReader/OutputStreamReader的说明,终于写出了下列的代码(这边只是给出了核心代码),解决了两个问题:
(1) 对文件添加BOM头
(2) 读取和写入文件时的编码指定
try {
FileInputStream fis = new FileInputStream("input.csv");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("output.csv");
fos.write(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
while((String line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
bw.close();
br.close();
} catch (IOException e) {
System.out.println(e.toString());
}
这段代码做了这些事情:
第2~4行:
这3行以UTF-8的格式打开了input.csv这个文件,并且最终创建了一个BufferedReader对象。
第6~9行:
这4行以UTF-8的格式打开了output.csv这个文件,最终创建了一个BufferedWriter对象。
其中第7行的操作,正是对文件写入BOM头的操作。
第10~13行:
这4行代码从input.csv文件逐行读取内容,输出到了output.csv。
必须要说一下这边让人觉得贴心的一点,如果一个UTF-8格式的文件,它已经存在一个BOM头了,这个BOM头也是不会被读取的,只有后面的字符会被读取到,实在是帮了大忙了。
第14~15行:
这两行做了BufferedReader和BufferedWriter两个对象的关闭。如果不关闭的话,可能会出现缓冲区数据的丢失。
值得一提的是,按照Javadoc的说法,如果关闭了这两个对象,那么和对象相关的文件流也会被释放。也就是说,一串关联的文件流的关闭只要做一次就可以了,只要关闭最末端的文件流就可以了。
这边说两句闲话>w<
虽说不关闭会出现异常是一个问题,可是促使我关闭文件流的主要原因,果然还是因为我入门的程序语言是C++。在用C++写一长篇复杂代码时,其中相对比较机械的垃圾回收就是我写程序的一个治愈点。Java剥夺了这种乐趣,为此我还失落了很久……
以上就是对这段小代码的详细解释。
从结论来说,如果在读取或者写入的文件中,有英语字符以外的内容,那么比较好的习惯,就是使用上边的代码,预先指定好编码的类型,而不是使用FileReader/FileWriter这两个封装了功能的类,否则就可能因为平台默认编码的不同造成乱码。
参考资料:
[1] Java FileReader encoding issue http://stackoverflow.com/questions/696626/java-filereader-encoding-issue
[2] Javadoc http://docs.oracle.com/javase/7/docs/api/

当使用Java处理UTF-8编码的CSV文件时,若没有BOM头,Windows下的Excel可能导致乱码。通过使用InputStreamReader和OutputStreamReader指定UTF-8编码,并在写入时添加BOM头,可以解决这个问题。文章介绍了错误的尝试,如直接使用FileReader/FileWriter导致的编码问题,并提供了正确实现的代码示例。

1383





