io with java

Table of Contents

目的

本文目的对本人对io的理解纠正

filechannel

BIO,NIO貌似NIO就是非阻塞,那么看如下代码:

        RandomAccessFile file = new RandomAccessFile("/home/paul/opensource/yutak-dev/demo.txt","rw");
        FileChannel channel = file.getChannel();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        buffer.clear();
        channel.read(buffer);
        buffer.flip();
        byte[] array = new byte[buffer.limit()];
        buffer.get(array);
        char[] chars = new char[array.length];
        for (int i = 0; i < array.length; i++) {
            chars[i] = (char) array[i];
        }
        System.out.println(String.valueOf(chars));
    

这是我的第一个误区,就是NIO不应该不阻塞吗? 实际上:
这里看了不少的blog,概念很多,我认为还是根据场景分析吧,记忆概念提升不高 IO对于调用者来说,阻塞就是调用代码时,需要立即获得结果,同步执行,我们经常作为client使用,比如等待sql,等待http response.etc就是这种同步使用,非阻塞的话实际上要使用异步api,CompletableFuture很经典,当然其存在whenCompleteAsyncapi,实际上就是非当前thread执行回调逻辑,下面这段code执行结果:world hello,其实client这边考虑的很简单,要么一直等待,要么通过事件驱动,挂载一个Handler,数据到达后通知

  • 异步阻塞
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 13;
        }).whenComplete((a,t)->{
            System.out.println(a);
        });
        TimeUnit.SECONDS.sleep(5);
  • 异步非阻塞
        CompletableFuture.supplyAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 13;
        }).whenComplete((a,t)->{
            System.out.println(a);
        });
        TimeUnit.SECONDS.sleep(10);

MappedByteBuffer

api角度上是针对fileChannelmap函数

    RandomAccessFile file = new RandomAccessFile("/home/paul/opensource/yutak-dev/demo.txt", "rw");
        MappedByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length());
        for (int i = 0; i < 1024 * 1024 * 1024; i += 4*1024) {
            buffer.get(i);
        }

fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);,进行映射是一个消耗极少的ops,此时并不意味着 1G 的文件被读进了pageCache

  1. mmap 映射的过程可以理解为一个lazy load,只有 get()时才会触发缺页中断,DMAload进OS
  2. 预读大小是由OS算法决定的,可以默认4kb,所以说如果想在这个实验中加载内存到pageCache实际上是要不断get()
  3. mmap的生命周期可以视为:map(映射),get/load (缺页中断),clean(回收),很多文章将mmap的提升归功于减少cpu拷贝,实际上我认为是用户态trap进入内核态的消除,最常见的情况就是鼠标的轮询率,高轮询率的时候,系统占用陡增,并且可能出现卡顿,然而如果说,对buffer的数据read(),虽然无需换态,但是依然需要从page cache读数据进入userCache,总结下,就是一段自定义的堆外内存
 public static void main(String[] args) throws Exception {
        long s = System.currentTimeMillis();
        do3();
        System.out.println("cost time mills:"+ (System.currentTimeMillis() - s));
    }
    public static void do1() throws Exception {
        RandomAccessFile file = new RandomAccessFile("/home/paul/opensource/yutak-dev/demo.txt", "rw");
        MappedByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length());
        for (int i = 0; i < 1024 * 1024 * 1024; i ++) {
            buffer.get(0);
        }
    }

    public static void do3() throws Exception {
        RandomAccessFile file = new RandomAccessFile("/home/paul/opensource/yutak-dev/demo.txt", "rw");
        FileChannel channel = file.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1);
        for (int i = 0; i < 1024 * 1024 * 1024 ; i ++) {
            channel.read(buffer);
        }
    }

这两段逻辑,如果调用次数较少,实际上是不分伯仲的,但是将次数调大,这个数据1GB,filechannel几乎停滞,本实验并不严谨,但是在模拟的情况下,二者已经跨越多个数量级,足以保证实验的基本正确性,,所以说,高并发的时候,换态性能损耗最大

file system

给出一张linux的图片供以后温习 alt text