测试环境
cpu: intel® core™ i7-8750H
内存:16G
硬盘:512 SSD
测试文件:四个文件的zip包,压缩后大小 1.73 GB
测试结果
流式IO读取样例代码
final int BUFFER_SIZE = 1024;
int i = 0;
byte[] bytes = new byte[BUFFER_SIZE];
File file2 = new File(filePath);
FileInputStream in2 = new FileInputStream(file2);
long begin3 = System.currentTimeMillis();
while (in2.read(bytes) > 0){
i++;
}
in2.close();
long end3 = System.currentTimeMillis();
System.out.println("count is:" + i);
System.out.println("time is:" + (end3 - begin3));
NIO读取样例代码
final int BUFFER_SIZE = 1024;
int i = 0;
File file = new File(filePath);
FileInputStream in = new FileInputStream(file);
FileChannel channel2 = in.getChannel();
ByteBuffer buff = ByteBuffer.allocate(BUFFER_SIZE);
long begin2 = System.currentTimeMillis();
while (channel2.read(buff) != -1) {
i++;
buff.flip();
buff.clear();
}
channel2.close();
long end2 = System.currentTimeMillis();
System.out.println("count is:" + i);
System.out.println("time is:" + (end2 - begin2));
内存映射读取样例代码
final int BUFFER_SIZE = 1024;
int i = 0;
byte[] bytes = new byte[BUFFER_SIZE];
RandomAccessFile memoryMappedFile = new RandomAccessFile(filePath, "rw");
FileChannel channel = memoryMappedFile.getChannel();
final long len = channel.size();
long begin = System.currentTimeMillis();
MappedByteBuffer out = channel.map(FileChannel.MapMode.READ_WRITE, 0, len);
for (int offset = 0; offset < len; offset += BUFFER_SIZE) {
i++;
if (len - offset >= BUFFER_SIZE) {
out.get(bytes);
} else {
out.get(bytes,0,(int) len - offset);
}
}
memoryMappedFile.close();
long end = System.currentTimeMillis();
System.out.println("count is:" + i);
System.out.println("time is: " + (end - begin));
测试结果
分别测试缓冲区为 1K,1M,10M.每种情况执行三次
buffer size | 流式IO | NIO | 内存映射 |
---|---|---|---|
1K | count is:1818681 time is:4717 | count is:1818681 time is:4873 | count is:1818681 time is:736 |
1K | count is:1818681 time is:4673 | count is:1818681 time is:4779 | count is:1818681 time is:733 |
1K | count is:1818681 time is:4700 | count is:1818681 time is:4731 | count is:1818681 time is:728 |
1M | count is:1777 time is:1139 | count is:1777 time is:459 | count is:1777 time is:757 |
1M | count is:1777 time is:1148 | count is:1777 time is:454 | count is:1777 time is:744 |
1M | count is:1777 time is:1103 | count is:1777 time is:461 | count is:1777 time is:764 |
10M | count is:178 time is:1293 | count is:178 time is:964 | count is:178 time is:830 |
10M | count is:178 time is:1263 | count is:178 time is:903 | count is:178 time is:845 |
10M | count is:178 time is:1241 | count is:178 time is:896 | count is:178 time is:840 |
结论
- 从表格可以看出总体来说传统流式IO读取速度最慢。
- 三种方式速度最快的情况都是1M,即缓冲区并非越大越快。
- 在缓冲区较小的时候流式IO和NIO都慢的无法忍受且读取速度相差不大。
- 使用内存映射方式无论缓冲区大小读取速度相对稳定。
- 在参数合适情况下NIO读取速度最快。
疑问
- 当缓冲区较小时为何NIO读取速度如此慢?
- 缓冲区大小为何对于内存映射影响较小?
解答
针对疑问一让我想到NIO read方法中的几行代码。打开read 函数源码就能找到。
sun.nio.ch.IOUtil#read(java.io.FileDescriptor, java.nio.ByteBuffer, long, sun.nio.ch.NativeDispatcher)
static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
if (var1.isReadOnly()) {
throw new IllegalArgumentException("Read-only buffer");
} else if (var1 instanceof DirectBuffer) {
return readIntoNativeBuffer(var0, var1, var2, var4);
} else {
ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
int var7;
try {
int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
var5.flip();
if (var6 > 0) {
var1.put(var5);
}
var7 = var6;
} finally {
Util.offerFirstTemporaryDirectBuffer(var5);
}
return var7;
}
}
从这里可以看出如果缓冲区不是直接内存JDK会创建一个大小相同的临时直接内存读取文件,再从临时的直接内存拷贝。因此修改代码使用直接内存作为缓冲区再次测试
// 使用直接内存作为缓冲区
ByteBuffer buff = ByteBuffer.allocateDirect(BUFFER_SIZE);
buffer size | NIO + 直接内存 | NIO |
---|---|---|
1K | count is:1818681 time is:4720 | count is:1818681 time is:4873 |
1K | count is:1818681 time is:4620 | count is:1818681 time is:4873 |
1K | count is:1818681 time is:4622 | count is:1818681 time is:4873 |
1M | count is:1777 time is:314 | count is:1777 time is:454 |
1M | count is:1777 time is:330 | count is:1777 time is:454 |
1M | count is:1777 time is:310 | count is:1777 time is:454 |
10M | count is:178 time is:514 | count is:178 time is:896 |
10M | count is:178 time is:510 | count is:178 time is:896 |
10M | count is:178 time is:510 | count is:178 time is:896 |
从测试结果看,使用直接内存作为缓冲区对于读取速度有一定提升,但是对于缓冲区很小的时候依然很慢,应该就是本身读取方式的问题了。
针对疑问二由于内存映射虽然不会完全将文件加载到内存,但是有预读在内存间拷贝所以速度很快,有一点不能解释,使用内存映射只是节省了数据从内核区拷贝到用户区和其他读取方式只是多了一次内存拷贝为何会慢如此多--------待发掘