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代码块提供了不必受到关闭流的特性).但是字节流关闭是为了不消耗资源,而字符流除此之外还有刷新缓冲区的作用,否则会造成数据丢失.两者的区别如下图:
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方法,返回结果是每行的文本和空字符串交替出现,但源文件里并没有空行.反复调试,最后是通过查看源文件的十六进制格式找到了问题:
图中右边的每个.代表了一个转义字符(或中文字符),相应的左边的十六进制表示就是 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,同样本次读取结束并换行.这样就多出了一个空行.
当然,发现问题的.txt是Android Studio生成的Monkey日志,可能对于换行符的跨平台性没有处理的比较好.正常以\r\n结束是不会出现这种情况的.除了文本文件的标准化,更好的解决办法是直接用字节流+数组的方式读取,速度是差不多的.有空可以放一下不同的方式读写数据的效率对比.