原创

Java NIO 笔记


Java NIO(New IO Non Blocking IO)

NIO可以以更加高效的方式进行读写操作

IONIO
面向流的(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
(无)选择器(Selector)

传统的方式 在这里插入图片描述

NIO方式

在这里插入图片描述

在这里插入图片描述

 缓冲区:在Java NIO中负责数据的传输,缓冲区就是数组。用于存储不同数据类型的数据
 根据数据类型不同(boolean除外,byte,short,char,long,int,float,double都有对应的),提供了相应类型的缓冲区
 管理方式基本一致,都是通过allocate() 获取缓冲区
 最常用的 ByteBuffer

用于存取数据的两个核心方法

  • put 存入数据到缓冲区中
  • get 获取缓冲区的数据

缓冲区的四个核心属性

  • capacity 容量,表示缓冲区中最大存储数据的容量 一旦声明不能改变

  • limit 界限 ,表示缓冲区中可以操作数据的大小 limit后面的数据是不能进行读写的

  • position 位置,表示缓冲区中正在操作数据的位置 position<=limit<=capacity

  • mark 标记,用于记录position的位置,用reset() 恢复到 mark的位置

在这里插入图片描述

	@Test
    public void test1(){
        String string = "abcd";
        //分配一个指定大小的缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        System.out.println("----------------allocate----------------");
        System.out.println("缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //利用put存入数据
        byteBuffer.put(string.getBytes());
        System.out.println("----------------put----------------");
        System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //调用filp方法从写数据模式切换到读数据模式
        Buffer flip = byteBuffer.flip();
        System.out.println(flip);
        System.out.println("----------------flip----------------");
        System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //利用get读取缓冲区中的数据
        byte[] bytes = new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);
        System.out.println("读取的数据是:"+new String(bytes,0,bytes.length));
        System.out.println("----------------get----------------");
        System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //rewind 可重复读数据
        byteBuffer.rewind();
        System.out.println("----------------rewind----------------");
        System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //clear清空缓冲区,但是缓冲区中的数据依然存在,只是这些数据处于被遗忘状态
        byteBuffer.clear();
        System.out.println("----------------clear----------------");
        System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
        System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
        System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
        //判断缓冲区中是否还有剩余的数据
        if (byteBuffer.hasRemaining()){
            //获取缓冲区中还可以操作的数量
            System.out.println("缓冲区中还可以操作的数量"+byteBuffer.remaining());
        }
    }
----------------allocate----------------
缓冲区的大小是:1024
可以操作数据的大小是:1024
当前正在操作数据的位置是:0
----------------put----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:1024
当前正在操作数据的位置是:4
java.nio.HeapByteBuffer[pos=0 lim=4 cap=1024]
----------------flip----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:0
读取的数据是:abcd
----------------get----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:4
----------------rewind----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:0
----------------clear----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:1024
当前正在操作数据的位置是:0
缓冲区中还可以操作的数量1024

直接缓冲区与非直接缓冲区

  • 非直接缓冲区:通过 allocate()方法分配的缓冲区,将缓冲区建立在JVM的内存中

在这里插入图片描述

  • 直接缓冲区:通过allocateDirect()方法分配的缓冲区,将缓冲区建立在操作系统上的物理内存中,可以提高效率

在这里插入图片描述

省略了 copy 的过程,所以提升了效率

通道

​ 用于源节点与目标节点的连接,在NIO中,主要针对于负责缓冲区中数据的传输,通道本身是不存储任何数据的,配合缓冲区进行传输

​ 通道主要实现类

  • java.nio.channels.Channel 接口:
    • FileChannel 本地的
    • SocketChannel TCP
    • ServerSocketChannel TCP
    • DatagramChannel UDP

获取通道

  1. Java 针对支持通道的类提供了getChannel() 方法

    1. 本地 IO

      • FileInputStream/FileOutputStream
      • RandomAccessFile
    2. 网络 IO

      • Socket
      • ServerSocket
      • DatagramSocket
  2. 在 JDK1.7 中的 NIO.2 针对各个通道提供了静态方法 open()

  3. 在 JDK1.7 中的 NIO.2的Files工具类的 newByteChannel() 方法

完成文件的复制

  1. 利用通道(非直接缓冲区)

    public class test1 {
        //利用通道完成文件的复制
        @Test
        public void channelTest(){
            FileInputStream inputStream = null;
            FileOutputStream outputStream = null;
            FileChannel inChannel = null;
            FileChannel outChannel = null;
            try {
                inputStream = new FileInputStream("");
                outputStream = new FileOutputStream("");
                //获取通道
                inChannel = inputStream.getChannel();
                outChannel = outputStream.getChannel();
                //传输数据
                //分配一个指定大小的缓存区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int len = 0;
                //将通道中的数据存入缓冲区
                while ((len=inChannel.read(buffer))!=-1){
                    //将缓冲区的数据写入通道
                    buffer.flip();
                    outChannel.write(buffer);
                    //清空缓冲区
                    buffer.clear();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (outChannel==null){
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inChannel==null){
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (outputStream==null){
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inputStream==null){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  2. 直接缓冲区的文件复制(内存映射的方式)

    public class test2 {
        @Test
        public void copyTest(){
            FileChannel inChannel = null;
            FileChannel outChannel = null;
            MappedByteBuffer inMapBuffer =null;
            MappedByteBuffer outMapBuffer = null;
            try {
                //获取通道
                inChannel = FileChannel.open(Paths.get(""), 
                                             StandardOpenOption.READ);
                outChannel = FileChannel.open(Paths.get(""),
                                              StandardOpenOption.READ,
                                              StandardOpenOption.WRITE);
                //复制的操作
                inMapBuffer =
                        inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
                outMapBuffer =
                        outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
                //直接对缓冲区进行数据的读写操作
                byte[] bytes = new byte[1024];
                inMapBuffer.get(bytes);
                outMapBuffer.put(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (outChannel==null){
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inChannel==null){
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  3. 通道之间的数据传输(直接缓冲区的方式)

    transferFrom()

    transferTo()

    public class Test3 {
        //通道之间的数据传输
        @Test
        public void copyTest(){
            FileChannel inChannel = null;
            FileChannel outChannel = null;
            try {
                inChannel =
                        FileChannel.open(Paths.get(""), StandardOpenOption.READ);
                outChannel =
                        FileChannel.open(Paths.get(""),StandardOpenOption.READ,StandardOpenOption.WRITE);
                //inChannel.transferTo(0,inChannel.size(),outChannel);
                outChannel.transferFrom(inChannel,0,inChannel.size());
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  4. 分散于聚集

    分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中

    聚集写入(Gathering Writes):将多个缓冲区的数据都聚集到通道中

在这里插入图片描述

在这里插入图片描述

public class Test4 {
    //分散和聚集的方式
    public void copyTest(){
        RandomAccessFile randomAccessFile1 = null;
        RandomAccessFile randomAccessFile2 = null;
        try {
            randomAccessFile1 = new RandomAccessFile("","rw"); //rw是读写模式
            //获取通道
            FileChannel fileChannel = randomAccessFile1.getChannel();
            //分配指定大小的缓冲区
            ByteBuffer buffer1 = ByteBuffer.allocate(1024);
            ByteBuffer buffer2 = ByteBuffer.allocate(1024);
            ByteBuffer buffer3 = ByteBuffer.allocate(1024);
            //分散读取
            ByteBuffer[] byteBuffers = new ByteBuffer[]{buffer1,buffer2,buffer3};
            for(ByteBuffer byteBuffer : byteBuffers){
                byteBuffer.flip();
            }
            System.out.println(new String(byteBuffers[0].array(),0,byteBuffers[0].limit()));
            System.out.println("-----------------------");
            System.out.println(new String(byteBuffers[1].array(),0,byteBuffers[1].limit()));
            System.out.println("-----------------------");
            System.out.println(new String(byteBuffers[2].array(),0,byteBuffers[2].limit()));
            //聚集写入
            randomAccessFile2 = new RandomAccessFile("","rw");
            FileChannel file2Channel = randomAccessFile2.getChannel();
            try {
                file2Channel.write(byteBuffers);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileChannel.read(byteBuffers);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                randomAccessFile2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                randomAccessFile1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字符集(Charset)

  • 编码:字符串--->字节数组
  • 解码:字节数组--->字符串

NIO的非阻塞式网络通信

  • 使用NIO完成网络通信的三个核心

    • 通道(Channel)负责连接

      • SocketChannel
      • ServerSocketChannel
      • DatagramChannel
    • 缓冲区(Buffer)负责数据的存取

    • 选择器(Selector)是 SelectableChannel 的多路复用器,用于监控 SelectableChannel 的 IO 状况的

在这里插入图片描述

  • 使用NIO完成网络通信

    • 阻塞式

      public class SocketChannelTest {
          //客户端
          @Test
          public void client(){
              //获取通道
              SocketChannel socketChannel =null;
              FileChannel inChannel = null;
              try {
                  //获取通道
                  socketChannel =
                          SocketChannel.open(new InetSocketAddress("127.0.0.1",9090));
                  //发送数据
                  //首先创建缓冲区
                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                  //读取文件并发送到服务端去
                  inChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ);
                  while ((inChannel.read(byteBuffer))!=-1){
                      byteBuffer.flip();
                      socketChannel.write(byteBuffer);
                      byteBuffer.clear();
                  };
              } catch (IOException e) {
                  e.printStackTrace();
              }finally {
                  try {
                      inChannel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  try {
                      socketChannel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          //客户端
          @Test
          public void server(){
              ServerSocketChannel socketChannel = null;
              FileChannel outChannel = null;
              try {
                  //获取通道
                  socketChannel = ServerSocketChannel.open();
      
                  outChannel =
                      FileChannel.open(Paths.get(""),StandardOpenOption.READ,StandardOpenOption.WRITE);
                  //绑定端口号
                  socketChannel.bind(new InetSocketAddress(9090));
                  //获取客户端的连接的通道
                  SocketChannel accept = socketChannel.accept();
                  //接收客户端的数据,并保存
                  //创建缓存区
                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                  //边读边写
                  while ((outChannel.read(byteBuffer))!=-1){
                      byteBuffer.flip();
                      outChannel.write(byteBuffer);
                      byteBuffer.clear();
                  }
             } catch (IOException e) {
                  e.printStackTrace();
              }finally {
                  try {
                      outChannel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  try {
                      socketChannel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
    • 非阻塞式

      public class NonBlockingNIOTest {
      
          @Test
          public void client(){
              SocketChannel socketChannel = null;
              try {
                  //获取通道
                  socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9090));
      
                  FileChannel fileChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ);
                  //切换成非阻塞式
                  socketChannel.configureBlocking(false);
                  //分配指定大小的缓冲区
                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                  //发送数据给服务端
                  while (fileChannel.read(byteBuffer)!=-1){
                      byteBuffer.flip();
                      socketChannel.write(byteBuffer);
                      byteBuffer.clear();
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }finally {
                  try {
                      socketChannel.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          @Test
          public void server(){
              //获取通道
              ServerSocketChannel serverSocketChannel = null;
              try {
                  //获取通道
                  serverSocketChannel = ServerSocketChannel.open();
                  //切换成非阻塞式
                  serverSocketChannel.configureBlocking(false);
                  //绑定连接
                  serverSocketChannel.bind(new InetSocketAddress(9090));
                  //获取选择器
                  Selector selector = Selector.open();
                  //将通道注册到选择器上,指定监听事件:接收事件
                  serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                  //通过选择器轮询获取已经准备就绪的事件
                  while (selector.select()>0){
                      //获取当前选择器中所有的注册的选择键(已经就绪的监听事件)
                      Set<SelectionKey> selectionKeys = selector.selectedKeys();//包含了所有注册的事件
                      //使用迭代器
                      Iterator<SelectionKey> selectionKey = selectionKeys.iterator();
                      while (selectionKey.hasNext()){
                          //获取准备就绪的事件
                          SelectionKey key = selectionKey.next();
                          //判断是什么事件准备就绪
                          if (key.isAcceptable()){
                              //若是接收状态就绪,获取客户端链接
                              SocketChannel socketChannel = serverSocketChannel.accept();
                              //切换非阻塞模式
                              socketChannel.configureBlocking(false);
                              //将该通道注册到选择器上
                              socketChannel.register(selector,SelectionKey.OP_READ);//监听读就绪事件
                          }else if (key.isReadable()){
                              //获取当前选择器上读就绪状态的通道
                              SocketChannel socketChannel = (SocketChannel) key.channel();
                              //读取数据
                              ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                              while ((socketChannel.read(byteBuffer))!=-1){
                                  byteBuffer.flip();
                                  socketChannel.write(byteBuffer);
                                  byteBuffer.clear();
                              }
                          }
                          //取消选择键 selectionKey
                          selectionKey.remove();
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      

      此时开启服务器,服务器会一直等待,哪个客户端准备就绪,就会接受哪个客户端的数据

后端
Java

评论