NIO简述

1.NIO 和 IO 区别

首先传统的io是面向流Stream Oriented,单向;阻塞io
NIO是面向缓冲区 Buffer Oriented 双向; 非阻塞Io;有选择器;
2.NIO

NIO 使用时通过通道和缓冲区进行同时工作的;可以将通道理解成火车到,缓冲区是火车;
1)缓冲区:实质是数据,用来存储的
除了boolean以外其他的都具有相对应的缓冲区;获得缓冲区XXXBuffer.allocate(int capacity);
缓冲区有四个属性;

  • 1.mark 用来标记当前位置的;可通过reset()方法回到标记位置
  • 2.position 当前位置,起始位置是0,
  • 3.limit 界限
  • 4.capacity 容量

存取方法put() get() 每次在读取的时候我们要进行flip()切换到读模式;
直接缓冲区和非直接缓冲区
《NIO简述》
直接缓冲区是直接在物理内存上进行存储;优点是非常快,缺点不受控制,放在物理内存上什么时候放在物理磁盘就不受程序控制;
直接缓冲区的获取:

1、static ByteBuffer allocateDirect(int )
2、FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 

2)通道:用来传输的;他是一个独立的控制器
获取通道的方法:

  • 1.通过getChannel()方法获得;获取本地的FileInputStream/FileOutputStream;RandomAccessFile ,获取网络的serverSocket ,datagramSocket;

    // 将1复制到2 上, 那么我们首先要通过
       try {
           FileInputStream is = new FileInputStream("1.jpg");
           FileOutputStream os = new FileOutputStream("2.jpg");
           // 得到通道
           FileChannel isChannel = is.getChannel();
           FileChannel osChannel = os.getChannel();
           // 通过缓冲区将进行复制
           ByteBuffer bf = ByteBuffer.allocate(1024);// 相当于原来的字节数组;
           //读取通道并且写入
           while(isChannel.read(bf)!=-1){
               bf.flip();osChannel.write(bf);
               bf.clear();
           }
           osChannel.close();
           isChannel.close();
           os.close();
           is.close();
       } catch (IOException e) {
           e.printStackTrace();
       } finally {
       }
  • 2.支持通道静态方法Open(); filechanne;socketchanne;serversocketchannel;datagramchannel

     public static void main(String[] args) {
     // 2使用直接内存方式复制;  ①使用内存映射文件;
       try {
           FileChannel is = FileChannel.open(Paths.get("d:/1.jpg"), StandardOpenOption.READ);
           FileChannel os = FileChannel.open(Paths.get("d:/2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
           //从内存映射文件中获取;
           MappedByteBuffer isMap = is.map(FileChannel.MapMode.READ_ONLY, 0, is.size());
           MappedByteBuffer osMap = os.map(FileChannel.MapMode.READ_WRITE, 0, is.size());
           // 直接对缓冲区数据进行操作
           byte[] bytes = new byte[isMap.limit()];
           isMap.get(bytes);
           osMap.put(bytes);
           is.close();
           os.close();
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
       }
    }
    
    
   public static void main(String[] args) {
  // 2使用直接内存方式复制;  ②使用通道
    try {
        FileChannel is = FileChannel.open(Paths.get("d:/1.jpg"), StandardOpenOption.READ);
        FileChannel os = FileChannel.open(Paths.get("d:/2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
   //   is.transferTo(0,is.size(),os);
        os.transferFrom(is,0,is.size());
        is.close();
        os.close();

    } catch (Exception e) {
        e.printStackTrace();
    } finally {

    }
}

分散与聚集
分散读取:用多个缓冲区去读取
聚集写入:将多个缓冲区的数据写入到一个通道

 public static void main(String[] args) {
    try {
        RandomAccessFile rw = new RandomAccessFile("1.txt", "rw");
        // 得到了通道
        FileChannel channel = rw.getChannel();
        // 继续获得缓冲区
        ByteBuffer by1 = ByteBuffer.allocate(100);
        ByteBuffer by2 = ByteBuffer.allocate(1024);
        //分散读取   多个缓冲区去读取
        ByteBuffer[] bfs = {by1,by2};
        channel.read(bfs);

        for(ByteBuffer b :bfs){
            b.flip();
        } 
        //聚集写入
        RandomAccessFile rw2 = new RandomAccessFile("2.txt", "rw");
        FileChannel channel1 = rw2.getChannel();
        channel1.write(bfs);

        channel.close();
        rw.close();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

NIO选择器
传统的线程为了避免客户端和服务器请求阻塞,采用的是多线程的方式,而现在NIO采用的就是选择器
将通道注册到选择器上,选择器换监视发送过来的请求是否准备就绪,准备就绪了才会将他分配到一个或多个线程上;
《NIO简述》

filechannel不能切换成非阻塞式
selectableChannel 下1.socketchannel,2.serversocketchannel–tcp 3.datagramchannel;—udp
还可以用于检测管段 pipe.sinkchannel;pipe.sourcechannel;
传统的阻塞式

public class NIOBlocking {
@Test
public void client() throws IOException {
    //1.获取通道
    SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
    FileChannel open1 = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
    //2.获得缓冲区
    ByteBuffer bbf = ByteBuffer.allocate(1024);
    //3.读取本地文件发送到服务器
    while(open1.read(bbf)!=-1){
        bbf.flip();
        open.write(bbf);
        bbf.clear();
    }
    //关闭流
    open1.close();
    open.close();
}

@Test
public void server() throws IOException {
    //1.获取通道
    ServerSocketChannel open = ServerSocketChannel.open();
    //为了保存到本地
    FileChannel open1 = FileChannel.open(Paths.get("3.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

    //2.绑定连接
    open.bind(new InetSocketAddress(8080));
    //3.获取客户端连接的通道
    SocketChannel accept = open.accept();

    //4.获取指定大小的缓冲区
    ByteBuffer bbf = ByteBuffer.allocate(1024);
    //5.接收客户端的数据保存到本地;
    while(accept.read(bbf)!=-1){
        bbf.flip();
        open1.write(bbf);
        bbf.clear();
    }
    //关闭通道
    accept.close();
    open1.close();
    open.close();
}

}
非阻塞式,也即是选择器式

public class NonBlocking {
@Test
public void client() throws IOException {
    //1.获取通道
    SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));
    //2.切换非阻塞
    open.configureBlocking(false);
    //3.获取缓冲区
    ByteBuffer bbf = ByteBuffer.allocate(1024);
    //4.向服务器发送数据
    bbf.put(new Date().toString().getBytes());
    bbf.flip();
    open.write(bbf);
    bbf.clear();
    //5.关闭通道
    open.close();
}
@Test
public void server() throws IOException {
    //1.获取通道
    ServerSocketChannel open = ServerSocketChannel.open();

    //2.切换成非阻塞模式
    open.configureBlocking(false);
    //3.绑定连接
    open.bind(new InetSocketAddress(8080));
    //4.获取选择器  --将通道注册到选择器上; ---- 循环比那里看选择器上是否有准备就绪事件
    Selector selector = Selector.open();
    open.register(selector, SelectionKey.OP_CONNECT);
    while(selector.select()>0){
        //5.获取选择器上所有注册的选择键(已经准备就绪的监听事件)
        Iterator<SelectionKey> it = selector.selectedKeys().iterator();
        while(it.hasNext()){
            SelectionKey sk = it.next();
            //6判断我们得到的selectionkey是不是就绪
            if(sk.isAcceptable()){
                //7如果是接收就绪 获得客户端通道  设置成false;将该通道注册的到选择器设置成读
                SocketChannel accept = open.accept();
                accept.configureBlocking(false);
                accept.register(selector,SelectionKey.OP_READ);
            }else if(sk.isReadable()){
                //8读就绪   获取通道
                SocketChannel channel = (SocketChannel)sk.channel();

                //9有通到了,我们想要进行读还需要缓存区
                ByteBuffer bbf = ByteBuffer.allocate(1024);
                int len=0;
                while((len=channel.read(bbf))!=-1){
                    bbf.flip();
                    System.out.println(new String(bbf.array(),0,len));
                    bbf.clear();
                }
            }
            //10关闭通道
            it.remove();
        }
    }
}

}

datagramchannel同理;就不在这里说了

pipe管道是连接两个线程之间的单项数据连接;sink写;source读;我们可以放在两个线程上一个写,一个读;

 @Test
public void test() throws IOException {
    //得到通道
    Pipe pipe = Pipe.open();
    //获取缓冲区
    ByteBuffer bbf = ByteBuffer.allocate(1024);
    bbf.put("马尾与小丑".getBytes());
    //向通道写数据
    Pipe.SinkChannel sink = pipe.sink();
    bbf.flip();
    sink.write(bbf);
    //---------------------- 上边可以放在一个线程,下边可以放在一个下线程;
    //读数据
    Pipe.SourceChannel source = pipe.source();
    bbf.flip();
    int len=source.read(bbf);
    System.out.println(new String(bbf.array(),0,len));

    source.close();
    sink.close();
}
    原文作者:zjdking
    原文地址: https://segmentfault.com/a/1190000020598888
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞