使用Java字符流读写文件遇到的问题

Java中的字符流可以很方便的读写文本文件.但是在使用中发现两个问题,编译环境JDK8:

1.执行到最后记得flush()或close()

这里用FileReader和FileWriter示例:


private static final String sourceName = "D:/MonkeyTestLog.txt";
private static final String outputName = "D:/output.txt";

    private static void fileReader() {
        File sourceFile = new File(sourceName);
        File outputFile = new File(outputName);

        Reader reader = null;
        Writer writer = null;
        try {
            reader = new FileReader(sourceFile);
            writer = new FileWriter(outputFile);
            int len;
            char[] buffer = new char[2048];
            while ((len = reader.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, len));
                writer.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
//            try {
//                writer.flush();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//            closeStreams(reader, writer);
        }
    }

    private static void closeStreams(Closeable... closeables) {
        for (Closeable closeable : closeables) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

如上面的代码,在finally代码块中注释掉关闭输入输入流的方法,则最终测试发现:目标文件会缺失原有文件的一部分文本.通过分析源码,最终刷新或者关闭流调用的是OutputStreamWriter的方法:

 /**
     * Flushes the stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void flush() throws IOException {
        se.flush();
    }

    public void close() throws IOException {
        se.close();
    }

这里又是调用的StreamEncoder类的方法,这个类包装了nio包的一些方法,最后还是通过OutputStream(字节流)将缓冲区中的剩余数据全部写入了目标文件.
要注意的是:不管使用字节流还是字符流,读写数据完成后都应该调用close()关闭流(Java7以后try代码块提供了不必受到关闭流的特性).但是字节流关闭是为了不消耗资源,而字符流除此之外还有刷新缓冲区的作用,否则会造成数据丢失.两者的区别如下图:

Java中的基本IO操作_20160825235002516.jpg

2.BufferedReader读取换行出现的异常

BufferedReader的特点是可以逐行读取字符串,其readLine()方法内部以换行符判断EOF.对应的BufferedWriter提供了newLine()进行换行.因为文本的换行读写很符合写作习惯,所以应用的不少.这里用于测试的源文件是app运行MonkeyTest生成的日志,生成的目标文件却是在原有基础上隔一行换一行.而用其它流拷贝都是正常的.情况类似下面:

源文件                                          目标文件
this firstline                               this firstline
event process                               
end                                         event process

                                               end

执行代码如下:

    private static void bufferReader() {
        File sourceFile = new File(sourceName);
        File outputFile = new File(outputName);

        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader(sourceFile));
            bw = new BufferedWriter(new FileWriter(outputFile));
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeStreams(br, bw);
        }
    }

显然由于多出的空行导致目标文件和源文件大小不一样,已经不能算拷贝成功了.反复调试发现每次执行readLine方法,返回结果是每行的文本和空字符串交替出现,但源文件里并没有空行.反复调试,最后是通过查看源文件的十六进制格式找到了问题:

Java中的基本IO操作_5.png

图中右边的每个.代表了一个转义字符(或中文字符),相应的左边的十六进制表示就是 0D 0D 0A,其ASCII表示就是\r\r\n.再回到readLine方法,注意到方法注释里有这么一句话:

* Reads a line of text.  A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

就是说读取文本中的一行.当遇到以下情况可以认为这一行读取结束:读取到\r,或者\n,或者\r\n.这样在读取上述文本的时候,因为先读取到了\r,那么本次读取结束,\r之前的内容被写成一行,换行.继续读取的时候碰到的一个字符是\r\n,同样本次读取结束并换行.这样就多出了一个空行.

扫描二维码关注公众号,回复: 2806487 查看本文章

当然,发现问题的.txt是Android Studio生成的Monkey日志,可能对于换行符的跨平台性没有处理的比较好.正常以\r\n结束是不会出现这种情况的.除了文本文件的标准化,更好的解决办法是直接用字节流+数组的方式读取,速度是差不多的.有空可以放一下不同的方式读写数据的效率对比.

猜你喜欢

转载自blog.csdn.net/wzhseu/article/details/80569286