程序中有一块儿功能是读取硬盘文件到内存里。这块儿代码执行得异常缓慢,读4个G左右的文件竟会用上十几分钟。平时也不着急,但是今天催得紧,就得优化下了。
上传代码大致如下:
1 2 3 4 5 6 7 8 |
public void readInByte(File file) throws IOException { FileInputStream input = new FileInputStream(file); int b = 0; // 每次循环读取一个字节并返回 while ((b = input.read()) != -1) { // action with b } } |
这里的问题在于直接使用了FileInputStream,就是说没有使用任何缓冲(buffer)。因此是一个字节一个字节地从磁盘里面读取文件,又一个字节一个字节地写到内存里,这样子效率可想而知。
尝试优化下,每次多读一些数据:
1 2 3 4 5 6 7 8 9 10 11 |
public void readInArray(File file) throws IOException { FileInputStream input = new FileInputStream(file); int len = 0; byte[] arr = new byte[1024]; // 将数据放到缓存数组中再返回 while ((len = input.read(arr)) != -1) { byte[] bytes = new byte[len]; System.arraycopy(arr, 0, bytes, 0, len); // action with bytes } } |
这样效果明显好了些,因为不再是一个字节一个字节的读了,而是每次读取了1024个字节的数据,也就是每1024字节内存才会与硬盘进行一次交互。因为大量减少了硬盘与内存的交互,速度自然就快了。
继续优化,使用BufferedInputStream:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void readWithBuffer(File file) throws IOException { FileInputStream input = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(input); int len = 0; byte[] buffer = new byte[1024]; // 这里通过BufferedInputStream利用了缓冲区来提速 while ((len = bis.read(buffer)) != -1) { byte[] bytes = new byte[len]; System.arraycopy(buffer, 0, bytes, 0, len); // action with bytes } } |
BufferedInputStream是FilterInputStream的子类,实现了装饰设计模式。BufferedInputStream是带缓冲区的输入流,默认缓冲区大小是8M,也就是说这个类会一次读取8M数据到内存缓存里以便进行后续操作。其原理与程序二差不多。
通过这三段程序分别测试读取了一个不太大的文件(1.57M),这三段程序的耗时分别是 8825、15、5,时间单位毫秒。
做一次调整:将程序二的缓存数组长度改为2048 * 1024,会看到执行时长也是5ms。
再做一次调整:这次读取一个34.6M的文件,并将程序二的缓存数组长度调整为8 * 1024 * 1024。可以看到程序二与程序三的执行时长分别是73ms与97ms。二者的性能还是比较接近的。
就这样!
发表评论