IO那些事03-几种常见的文件IO模型

java中的普通write和buffer read的重要差距?

每次写数据data:“123456789\n”

通过查看追踪系统调用线程文件,发现普通write,每一次write,实质都是一次系统调用,也就是都会发生一次内核态切换:
在这里插入图片描述
在这里插入图片描述

通过查看追踪系统调用线程文件,发现buffered的write,每一次write,写的是一个缓冲区的若干个“123456789\n”:

在这里插入图片描述

NIO

在jdk新版,除了传统IO,又出现了新的IO,也就是NIO,也暴露了新的API,ByteBuffer

 public  void whatByteBuffer(){

//        ByteBuffer buffer = ByteBuffer.allocate(1024);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);


        System.out.println("postition: " + buffer.position());
        System.out.println("limit: " +  buffer.limit());
        System.out.println("capacity: " + buffer.capacity());
        System.out.println("mark: " + buffer);

        buffer.put("123".getBytes());

        System.out.println("-------------put:123......");
        System.out.println("mark: " + buffer);

        buffer.flip();   //读写交替

        System.out.println("-------------flip......");
        System.out.println("mark: " + buffer);

        buffer.get();

        System.out.println("-------------get......");
        System.out.println("mark: " + buffer);

        buffer.compact();

        System.out.println("-------------compact......");
        System.out.println("mark: " + buffer);

        buffer.clear();

        System.out.println("-------------clear......");
        System.out.println("mark: " + buffer);

    }

打印:

postition: 0
limit: 1024
capacity: 1024
mark: java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]
-------------put:123......
mark: java.nio.DirectByteBuffer[pos=3 lim=1024 cap=1024]
-------------flip......
mark: java.nio.DirectByteBuffer[pos=0 lim=3 cap=1024]
-------------get......
mark: java.nio.DirectByteBuffer[pos=1 lim=3 cap=1024]
-------------compact......
mark: java.nio.DirectByteBuffer[pos=2 lim=1024 cap=1024]
-------------clear......
mark: java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]
//ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

ByteBuffer,堆上分配或者堆外分配。

System.out.println("postition: " + buffer.position());
System.out.println("limit: " +  buffer.limit());
System.out.println("capacity: " + buffer.capacity());
System.out.println("mark: " + buffer);
postition: 0
limit: 1024
capacity: 1024
mark: java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]

在这里插入图片描述

ByteBuffer有三大关键属性,pos代表偏移量,limit代表大小限制,cap代表文件总大小

put操作:

buffer.put("123".getBytes());
System.out.println("-------------put:123......");
System.out.println("mark: " + buffer);
-------------put:123......
mark: java.nio.DirectByteBuffer[pos=3 lim=1024 cap=1024]

put写入123后,pos偏移量前移3个

flip操作:

buffer.flip();   //读写交替
System.out.println("-------------flip......");
System.out.println("mark: " + buffer);
-------------flip......
mark: java.nio.DirectByteBuffer[pos=0 lim=3 cap=1024]

flip操作,将偏移量置为0,限制指针置为3,这是为了从写模式改为读模式,防止读的超过了可用的总大小,因此将limit限制为当前可读的最大值。
limit变成pos,pos归0

get操作:

buffer.get();
System.out.println("-------------get......");
System.out.println("mark: " + buffer);
-------------get......
mark: java.nio.DirectByteBuffer[pos=1 lim=3 cap=1024]

读取1个字节操作,会移动pos偏移量1

compact操作:

buffer.compact();
System.out.println("-------------compact......");
System.out.println("mark: " + buffer);
-------------compact......
mark: java.nio.DirectByteBuffer[pos=2 lim=1024 cap=1024]

在这里插入图片描述

取走开头的pos个字节:
在这里插入图片描述

compact后续字节进行前移:
在这里插入图片描述
在这里插入图片描述

将已经读取的偏移量所属文件内容积压出去,此时,limit指向文件最大。 pos左侧为未读空间,右侧为未写空间

clear操作:

buffer.clear();
System.out.println("-------------clear......");
System.out.println("mark: " + buffer);
-------------clear......
mark: java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]

一切重置成最开始的状态

对比文件的几种读取方式

public static void testRandomAccessFileWrite() throws Exception {

        RandomAccessFile raf = new RandomAccessFile(path, "rw");

        //普通的write操作
        raf.write("hello darknessa\n".getBytes());
        raf.write("hello seanzhou\n".getBytes());
        System.out.println("write------------");
        System.in.read();
        
        //基于指定偏移量的write操作
        raf.seek(4);
        raf.write("ooxx".getBytes());

        System.out.println("seek---------");
        System.in.read();
        
        //接下来开始NIO的操作:
        
        //获取文件的channel
        FileChannel rafchannel = raf.getChannel();
        
        //来得到一个通过mmap与内核pagecache直接映射的文件对象,也就是mmap
        //只有文件的channel能进行mmap,因为文件是块设备,所以才能使得pagecache和文件地址映射起来
        MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);

        //与上面的写入操作不太,这再是系统调用,但是数据会到达内核的pagecache
        map.put("@@@".getBytes());  
        //曾经我们是需要out.write()  这样的系统调用,才能让程序的data 进入内核的pagecache
        //系统调用必须有用户态内核态切换的过程
        //但mmap的内存映射,依然受内核的pagecache体系所约束的!!!
        //换言之,还是会丢数据
        //你可以去github上找一些 其他C程序员写的jni扩展库,使用linux内核的Direct IO
        //直接IO是忽略linux的pagecache
        //。。。但这样会有一系列复杂问题:
        //是把pagecache交给了程序自己去开发,开辟一个字节数组当作pagecache,动用代码逻辑来维护一致性/dirty
        //而除了更细粒度的控制之外,自己开发的与pagecache无太大区别,像mysql这种一般的使用Direct IO
        System.out.println("map--put--------");
        System.in.read();

        //  相当于传统IO模型的flush操作
//        map.force(); 


        raf.seek(0);

        ByteBuffer buffer = ByteBuffer.allocate(8192);
//        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        //将刚通过mmap写入的文件内容再写入到ByteBuffer中
        int read = rafchannel.read(buffer);   //buffer.put()
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);

        for (int i = 0; i < buffer.limit(); i++) {
            Thread.sleep(200);
            System.out.print(((char) buffer.get(i)));
        }


    }

在这里插入图片描述
在这里插入图片描述

可以看到txt类型的,就是我们的java代码段。
在这里插入图片描述

有2个文件描述符,第一个是基于mmap的生成的文件描述符,第二个是传统IO的需要通过write,read操作的文件描述符。

小结文件的IO模型

在这里插入图片描述

在linux中,每个进程都有一份完整独立的text区,data区,堆区,栈区,mmap映射区。
而java其实本质就是一个普通的linux进程,所以自然也有上述东西。

堆内

在linux级别的堆上分配了一块固定大小的专属于JVM使用的堆,这个就叫做堆内空间。 堆内如果想要写IO到磁盘,需要先复制一份到堆外内存。

堆外

在JAVA进程中的堆内存里,除了被JVM管辖固定大小的那块JVM堆外,剩下的堆内存,都叫堆外内存。

而上述两者不管如何,都还是基于用户态程序内存空间中的,想要实现IO调用,依然需要走系统调用,也就是内核态切换!

MMAP内存映射

在一个linux进程中,与堆区这些同等维度存在的,还有一个mmap映射区,通过它调用的IO操作,可以直接一一对应映射到内核中的PageCache中(虽然堆内堆外本质也是会最终缓存在pagecache,但映射关系不是一一对应的),这个过程不存在系统调用,而pagecache再通过与磁盘文件本身的映射关系,进行写入,这个时候的IO操作就是像是直接通过用户态来完成了以往的内核态敏感操作。 本质是内核和用户态的线性地址和逻辑地址映射到同一物理地址

最终,说到底,还是受内核pagecache的约束,也就不难得出结论:没有完全可靠的IO操作,因为pagecache本身的刷写机制本身就存在丢失的风险性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值