暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

java开发必会的NIO知识

bug生产基地 2021-04-12
534

NIO是同步非阻塞

阻塞与非阻塞的区别:

  • 阻塞时,在调用结果返回时,当前线程会被挂起,并在得到结果之后返回

  • 非阻塞时,如不能立即得到结果,该调用不会阻塞当前线程,调用者需要定时轮询查看处理状态

Channel(通道)和Buffer(缓冲区)

与普通IO的不同和关系

通道的概念

通道是对原I/O包中的流的模拟。到任何目的地的所有数据都必须通过一个Channel对象(通道)。一个Buffer实质上就是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;从通道中读取的任何数据都要读到缓冲区中

缓冲区的概念

  • Buffer是一个对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,在流式IO中,将数据直接写入或者读到Stream对象中

  • 在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区的。任何时候访问NIO中的数据,都需要将它放到缓冲区中

  • 缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程


    ByteBuffer
    CharBuffer
    ShortBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer
    复制


    选择器

    Selector是多路复用器,用于同时检测多个通道的事件以实现异步I/O。

    通过一个选择器来同时对多个套接字通道进行监听,当套接字通道有可用的事件的时候,通道改为可用状态,选择器就可以实现可用的状态。


    工作原理

    客户端-----》Channel-----》Selector------》keys--状态改变---》server


    Buffer  缓冲区

    Channel 通道

    Selector  选择器


    Server端创建ServerSocketChannel    有一个Selector多路复用器  轮询所有注册的通道,根据通道状态,执行相关操作


    • Connect  连接状态

    • Accept   阻塞状态

    • Read     可读状态

    • Write    可写状态



    Client端创建SocketChannel  注册到Server端的Selector 


    buffer

    • capacity  缓冲区数组的总长度

    • position  下一个要操作的数据元素的位置

    • limit  缓冲区数组中不可操作的下一个元素的位置,limit<=capacity

    • mark  用于记录当前position的前一个位置或者默认是0

    • clear/flip/rewind等都是操作limit和position的值来实现重复读写的



    ByteBuffer

    有且仅有ByteBuffer(字节缓冲区)可以直接与通道交互。


      public static void main(String[] args) {
      //生成FileChannel文件通道 FileChannel的操作--> 操作ByteBuffer用于读写,并独占式访问和锁定文件区域




      // 写入文件
      try(FileChannel fileChannel = new FileOutputStream(FILE).getChannel()){
      fileChannel.write(ByteBuffer.wrap("test".getBytes()));
      } catch (IOException e){
      throw new RuntimeException("写入文件失败",e);
      }
      // 在文件结尾写入
      try(FileChannel fileChannel = new RandomAccessFile(FILE,"rw").getChannel()){
      fileChannel.position(fileChannel.size());//移至文件结尾
      fileChannel.write(ByteBuffer.wrap("some".getBytes()));
      } catch (IOException e){
      throw new RuntimeException("写入文件结尾失败",e);
      }


      try(FileChannel fileChannel = new FileInputStream(FILE).getChannel();
      FileChannel out = new FileOutputStream("C:\\Users\\sinosoft\\Desktop\\copy.txt").getChannel()
      ){
      // 读取操作,需要调用allocate显示分配ByteBuffer
      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
      // read之后将数据放入缓冲区
      while (fileChannel.read(byteBuffer) != -1){
      byteBuffer.flip(); // 准备写入
      out.write(byteBuffer);
      byteBuffer.clear(); // 清空缓存区
      }
      } catch (IOException e){
      throw new RuntimeException("读取文件失败",e);
      }
      }
      复制

      方法说明

      rewind()方法是将position设置为缓冲区的开始位置


      get()和put()都会修改position


      get(int)和put(int)都不会修改position


      mark()设置mark为当前position


      flip()将写模式切换为读模式

        public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
        }
        复制

        内存映射文件

        内存映射文件可以创建和修改那些因为太大而无法放入内存的文件。

          RandomAccessFile tdat = new RandomAccessFile("test.dat", "rw");
          MappedByteBuffer out = tdat.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);


          或者
          FileChannel fc = new FileInputStream(new File("temp.tmp")).getChannel();
          IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY,0, fc.size()).asIntBuffer();
          复制

          映射文件访问比标准IO性能高很多

          文件锁定

          文件锁定可同步访问,文件锁对其他操作系统进程可见,因为java文件锁直接映射到本机操作系统锁定工具。


            public class FileLockTest {
            private static final String FILE = "C:\\Users\\sinosoft\\Desktop\\剩余工作副本.txt";
            public static void main(String[] args) throws IOException, InterruptedException {
            FileChannel fileChannel = new FileOutputStream(FILE).getChannel();


            // 文件锁
            FileLock fileLock = fileChannel.tryLock();
            Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            FileChannel fileChannel = null;
            try {
            fileChannel = new FileOutputStream(FILE).getChannel();
            } catch (FileNotFoundException e) {
            e.printStackTrace();
            }
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("aqws".getBytes());
            try {
            System.out.println("线程准备写");
            fileChannel.write(byteBuffer);
            System.out.println("线程写完");
            } catch (IOException e) {
            e.printStackTrace();
            }
            }
            });
            thread.start();
            if(fileLock != null){
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("aqwqdqdhwfwihfejfhi".getBytes());
            System.out.println("主线程睡眠");
            Thread.sleep(10000);
            // 会报错 java.nio.channels.NonWritableChannelException
            // fileChannel.read(byteBuffer);
            System.out.println("主线程准备写");
            fileChannel.write(byteBuffer);
            fileLock.release();
            }
            }
            }






            主线程睡眠
            线程准备写
            java.io.IOException: 另一个程序已锁定文件的一部分,进程无法访问。
            at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
            at sun.nio.ch.FileDispatcherImpl.write(FileDispatcherImpl.java:75)
            at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
            at sun.nio.ch.IOUtil.write(IOUtil.java:65)
            at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:211)
            at com.zhanghe.study.io.nio.FileLockTest$1.run(FileLockTest.java:35)
            at java.lang.Thread.run(Thread.java:745)
            主线程准备写
            复制

            通过调用FileChannel上的tryLock或lock,可以获得整个文件的FileLock(SocketChannel、DatagramChannel和ServerSocketChannel不需要锁定,因为本质上就是单线程实体)


            tryLock()是非阻塞的,试图获取锁,若不能获取,只是从方法调用返回


            lock()会阻塞,直到获得锁,或者调用lock()的线程中断,或者调用lock()方法的通道关闭。


            使用FileLock.release()释放锁

              // 锁定文件的一部分,锁住size-position区域。第三个参数指定是否共享此锁
              tryLock(long position, long size, boolean shared)
              复制


              文章转载自bug生产基地,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论