NIO三件套

NIO三件套

所谓NIO三件套指的是

  1. Buffer 缓冲区
  2. Channel 通道
  3. Selector 选择器

NIO和传统BIO的区别

传统IO是面向的是数据流动 所以说BIO是面向流的 而且是单向的
而NIO是面向Buffer的 就是缓冲区 如果说传统IO是水流
那么NIO就是一条铁路 铁路本身是不能运东西的(不能传输数据) 需要上面的火车
先把货物装在火车上 然后才能运输 而且是双向运输
而Selector就是这个火车的质检员 和操作者

Buffer

沿用上面的比喻 这个Buffer就是这个火车 装载着数据

Buffer初体验

先来简单看一下层级 实现类太多 这里只展示一部分
在这里插入图片描述
Buffer 缓冲区,其实他没有多么神秘,不管什么实现类 底层都是一个数组 根据具体的实现类不同 数组类型也不同
在这里插入图片描述
在这里插入图片描述

Buffer中维护着三个主要int 变量

 	private int mark = -1;   //标记位置
    private int position = 0;  //位置 表示缓冲区中正在操作数据的位置
    private int limit;    //界限 表示缓冲区中可以操作数据的大小 就是limit后面的数据是不能读写的
    private int capacity; //容量

不同的获得方式 都是通过allocate()方法得到缓冲区
缓冲区的两个核心方法 put() get()
举个例子

public class BufferTest {
    public static void main(String[] args) throws IOException {
        String str = "abcde";
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println("==========初始化===========");
        printBuffInfo(buffer);
        buffer.put(str.getBytes());
        System.out.println("==========put==========");
        printBuffInfo(buffer);
        buffer.flip();
        System.out.println("==========flip==========");
        printBuffInfo(buffer);
        byte[] box = new byte[buffer.limit()];
        buffer.get(box);
        System.out.println("==========get==========");
        printBuffInfo(buffer);
        buffer.rewind();
        System.out.println("==========rewind==========");
        printBuffInfo(buffer);
        buffer.clear();
        System.out.println("==========clear==========");
        printBuffInfo(buffer);
    }
    public static  void printBuffInfo(Buffer buffer){
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());
    }
}

输出结果如下

在这里插入图片描述
简述一个流程图:
在这里插入图片描述

这里还有一个mark属性 这个属性可以使用mark()方法来 进行标记 通过reset() 方法把position的位置移动到mark位置上
在这里插入图片描述
在这里插入图片描述
源码已经写的相当清晰了

直接缓冲区和非直接缓冲区

应用程序是不能直接从磁盘读取数据的 无论是读数据还是写数据 都要经过一次copy
而直接缓冲区是直接建立在物理内存中 磁盘和应用程序面对的直接是物理内存 省区了copy的内容 所以速度更快 但是提高了效率的同时存在两个弊端

  1. 首先安全性不能保障 因为应用程序直接操作的物理内存 所以具体什么时候写入磁盘完全交给了操作系统决定
  2. 第二是资源消耗上 直接缓冲区比非直接缓冲区更加消耗资源 资源分配和回收相对不可控
    在这里插入图片描述

在这里插入图片描述

创建直接缓冲区的方法和非直接缓冲区的方法和使用方式基本相同 只是方法不同

 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);

MappedByteBuffer

这里有一个特殊的缓冲区 就是Mapped缓冲区 比如 MappedByteBuffer
这东西比较牛逼 可以把文件直接映射到内存中 直接从内存中操作数据 用一段代码演示

 public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("D:\\1.txt", "rw");
        FileChannel fileChannel = raf.getChannel();
        String str = "随便写点啥哈";
        MappedByteBuffer mbb = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, str.getBytes().length+1);
        mbb.put(str.getBytes());
        raf.close();
    }

在这里我没有进行任何的写操作 单单操作了缓冲区 但是文件却改变了
在这里插入图片描述
在这里插入图片描述
我的文件确确实实是被改变了

Channel 通道

还是这个火车模型 Channel就是铁路 它连接着需要沟通的各个地点 但是必须和火车一起用(火车就是Buffer)
在这里插入图片描述

Channel主要可以分为两类

  1. 本地io:
       FileInputStreanm/FileOutputStream
      RandomAccessFile
  2. 网络io:
      Socket
      ServerSocket
      DatagramSocket

获取通道

任何支持通道的类都可以用getChannel()方法获得通道

接下来使用NIO完成一个文件的复制(非直接缓冲区)


        try (
                //正好用刚刚MappedBuffer改变的txt文件
                FileInputStream fis = new FileInputStream(new File("D:/1.txt"));
                FileOutputStream fos = new FileOutputStream("D:/2.txt");
                //获取通道
                FileChannel inChannel = fis.getChannel();
                FileChannel outChannel = fos.getChannel();
        ) {
            //创建火车(Buffer)
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //把通道的数据放在火车上
            while (inChannel.read(buffer) != -1) {
                buffer.flip();//切换到读数据模式 之前buffer写过
                //然后写到通道中
                outChannel.write(buffer);
                buffer.clear();//清空  position limit复位
            }
        } catch (IOException e) {
            e.printStackTrace();
        }//用了try with catch的语法糖 所以不需要关闭流 其实字节码里面是关闭了的
    

接下来同样 用直接缓冲区 就是上面那个映射MappedByteBuffer

 try(FileChannel inChannel =  FileChannel.open(Paths.get("D:/1.txt"), StandardOpenOption.READ);
            FileChannel outChannel =  FileChannel.open(Paths.get("D:/3.txt"), StandardOpenOption.WRITE,
            StandardOpenOption.CREATE,StandardOpenOption.READ)){//注意这里要加一个读模式 因为Mapped的out也需要读
            //根据输入的管道大小创建两个缓冲区
            MappedByteBuffer inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
            byte[] bytes = new byte[inMapBuffer.limit()];
            inMapBuffer.get(bytes);
            outMapBuffer.put(bytes);
        }catch (IOException e){
            e.printStackTrace();
        }

transferTo transferFrom

这两个方法是可以直接在管道中传输数据的 直接上代码

  try (FileChannel inChannel = FileChannel.open(Paths.get("D:/1.txt"), StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(Paths.get("D:/5.txt"), StandardOpenOption.WRITE
                     , StandardOpenOption.CREATE, StandardOpenOption.READ)) {
            inChannel.transferTo(0,inChannel.size() ,outChannel );
            outChannel.transferFrom(inChannel,0 , inChannel.size());
            //这两个方法是等价的
        } catch (IOException e) {
            e.printStackTrace();
        }

直接缓冲区会出现代码执行结束之后 资源没被释放的情况 具体什么时候释放不由我们控制,交给了系统决定

Selector 选择器

在NIO中使用了多路复用的模式
选择器承担了一个分发和判断的作用
首先将通道注册进选择器中
选择器会判断通道是否达到要求 是否可读等等内容
他是一个枢纽 可以理解为火车站 总控中心

发布了24 篇原创文章 · 获赞 10 · 访问量 3082

猜你喜欢

转载自blog.csdn.net/qq_43091847/article/details/103924781