Java对UTF-8格式文件的读取、写入及添加BOM头处理

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

        _(:з」∠)_连我自己都觉得这个标题略长了……


        事情的起因是这样的:

        数据管理系统可以自动导出编码为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/

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.9

TensorFlow-v2.9

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值