Java的IO性能调整
周sir |
2015年4月26日 |
输入输出(IO)操作 |
0 条评论 | 1208
本文多数技巧围绕磁盘文件 I/O,但同样适合网络 I/O 和窗口输出。先看底层读写,再谈压缩、格式化和串行化等高级问题;不涉及搜索算法、数据结构,也不谈系统级文件高速缓冲。
Java 对磁盘文件有两套结构:基于字节流的,以及基于字符序列的。Java 里一个字符占两个字节,不像 C 那样一个字节就是一个字符,所以从文件读字符时必须转换。这个差别在下面的计时例子里很明显。
一、低级与高级 I/O 清单
低级 I/O 相关问题:缓冲、读写文本文件、格式化的代价、随机访问。高级 I/O 问题:压缩、高速缓冲、分解、串行化、获取文件信息。
加速 I/O 的基本规则:避免访问磁盘;避免访问底层操作系统;避免方法调用;避免逐个处理字节和字符。这些规则当然不能处处都避开,否则就没有实际 I/O 了。
二、用缓冲减少读写次数
对 1 MB 输入文件,三种读法的执行时间差距很大。最慢和最快大约是 17 比 1。巨大加速并不等于永远该用“自己做缓冲”的第三种:文件结束处理稍不小心就会写错,可读性也不如包装流。方法 2 对多数应用才是“正确”选择。
| 方法 |
做法 |
约 1MB 耗时 |
优点 / 缺点 |
| 1 |
FileInputStream.read() 每次一字节,无缓冲 |
6.9 秒 |
编码简单,适合小文件;底层调用极多,不适合大文件 |
| 2 |
BufferedInputStream 包装后再逐字节读 |
0.9 秒 |
避免每个字节都打到底层;占用少量内存 |
| 3 |
FileInputStream.read(buf) 读入自备缓冲区 |
0.4 秒 |
最快;编码稍复杂、可读性差 |
| 4 |
按文件长度一次读完全文件 |
取决于内存 |
底层读取最少;大文件可能耗尽内存 |
方法 2 和 3 都用了缓冲:大块从磁盘读入,再按字节或字符访问。缓冲是加速 I/O 的基本技术。字节用 BufferedInputStream,字符用 BufferedReader。典型 Java 缓冲区是 1024 或 2048 字节;再加大约能再快 5%~10%,比重很小。
三、方法 1:逐字节 read
FileInputStream 的 read() 每次取下一个字节,会触发大量底层运行时调用。
import java.io.*;
public class intro1 {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
try {
FileInputStream fis = new FileInputStream(args[0]); // 建立指向文件的读写流
int cnt = 0;
int b;
while ((b = fis.read()) != -1) { // 每次读取一个字节
if (b == '\n')
cnt++;
}
fis.close();
System.out.println(cnt);
} catch (IOException e) {
System.err.println(e);
}
}
}
四、方法 2:BufferedInputStream
把文件字节块读入缓冲区,再每次取一个字节。每填满一次缓冲才访问一次底层存储。
import java.io.*;
public class intro2 {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
try {
FileInputStream fis = new FileInputStream(args[0]);
BufferedInputStream bis = new BufferedInputStream(fis); // 文件流指向缓冲区
int cnt = 0;
int b;
while ((b = bis.read()) != -1) { // 按块读入缓冲区,再每次取一个字节
if (b == '\n')
cnt++;
}
bis.close();
System.out.println(cnt);
} catch (IOException e) {
System.err.println(e);
}
}
}
五、方法 3:直接缓冲
用 fis.read(buf) 把字节块直接读进 buf,再在数组里扫换行。速度最快,但要自己处理读入长度 n。
import java.io.*;
public class intro3 {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
try {
FileInputStream fis = new FileInputStream(args[0]);
byte buf[] = new byte[2048];
int cnt = 0;
int n;
while ((n = fis.read(buf)) != -1) { // 直接读入字节块到 buf
for (int i = 0; i < n; i++) {
if (buf[i] == '\n')
cnt++;
}
}
fis.close();
System.out.println(cnt);
} catch (IOException e) {
System.err.println(e);
}
}
}
六、方法 4:缓冲整个文件
极端情况:先取文件长度,再一次读完。文件变成字节数组,很方便;问题是巨大文件可能没有足够内存。
import java.io.*;
public class readfile {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
try {
int len = (int)(new File(args[0]).length());
FileInputStream fis = new FileInputStream(args[0]);
byte buf[] = new byte[len]; // 建立直接缓冲
fis.read(buf); // 读取整个文件
fis.close();
int cnt = 0;
for (int i = 0; i < len; i++) {
if (buf[i] == '\n')
cnt++;
}
System.out.println(cnt);
} catch (IOException e) {
System.err.println(e);
}
}
}
七、终端输出与参考
缓冲的另一面是向窗口终端输出文本。默认情况下,System.out(一个 PrintStream)是行缓冲的:遇到换行符后才提交输出缓冲区。
转贴来源:http://www.uml.org.cn/zjjs/201006121.asp http://blog.csdn.net/Dbzen/archive/2008/11/22/3350325.aspx
一句话总结:I/O 要快,先减少磁盘和系统调用:优先 Buffered 包装流,必要时自备缓冲区;整文件读入只适合内存装得下的情况。
转载请注明来源:Java的IO性能调整
我是周sir,这是我的博客。致力于分享我学会的技术和优秀的文章。微信公众号 “周sir专栏” 欢迎大家关注。