流式IO,NIO,内存映射读取速度测试简单分析

测试环境

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流式IONIO内存映射
1Kcount is:1818681
time is:4717
count is:1818681
time is:4873
count is:1818681
time is:736
1Kcount is:1818681
time is:4673
count is:1818681
time is:4779
count is:1818681
time is:733
1Kcount is:1818681
time is:4700
count is:1818681
time is:4731
count is:1818681
time is:728
1Mcount is:1777
time is:1139
count is:1777
time is:459
count is:1777
time is:757
1Mcount is:1777
time is:1148
count is:1777
time is:454
count is:1777
time is:744
1Mcount is:1777
time is:1103
count is:1777
time is:461
count is:1777
time is:764
10Mcount is:178
time is:1293
count is:178
time is:964
count is:178
time is:830
10Mcount is:178
time is:1263
count is:178
time is:903
count is:178
time is:845
10Mcount is:178
time is:1241
count is:178
time is:896
count is:178
time is:840

结论

  1. 从表格可以看出总体来说传统流式IO读取速度最慢。
  2. 三种方式速度最快的情况都是1M,即缓冲区并非越大越快。
  3. 在缓冲区较小的时候流式IO和NIO都慢的无法忍受且读取速度相差不大。
  4. 使用内存映射方式无论缓冲区大小读取速度相对稳定。
  5. 在参数合适情况下NIO读取速度最快。

疑问

  1. 当缓冲区较小时为何NIO读取速度如此慢?
  2. 缓冲区大小为何对于内存映射影响较小?

解答

针对疑问一让我想到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 sizeNIO + 直接内存NIO
1Kcount is:1818681
time is:4720
count is:1818681
time is:4873
1Kcount is:1818681
time is:4620
count is:1818681
time is:4873
1Kcount is:1818681
time is:4622
count is:1818681
time is:4873
1Mcount is:1777
time is:314
count is:1777
time is:454
1Mcount is:1777
time is:330
count is:1777
time is:454
1Mcount is:1777
time is:310
count is:1777
time is:454
10Mcount is:178
time is:514
count is:178
time is:896
10Mcount is:178
time is:510
count is:178
time is:896
10Mcount is:178
time is:510
count is:178
time is:896

从测试结果看,使用直接内存作为缓冲区对于读取速度有一定提升,但是对于缓冲区很小的时候依然很慢,应该就是本身读取方式的问题了。
针对疑问二由于内存映射虽然不会完全将文件加载到内存,但是有预读在内存间拷贝所以速度很快,有一点不能解释,使用内存映射只是节省了数据从内核区拷贝到用户区和其他读取方式只是多了一次内存拷贝为何会慢如此多--------待发掘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值