• 流(43)


    NIO

    从JDK1.4开始Java引入了一系列改进的输入/输出处理的新功能,统称为NIO,即新IO,新增了许多用于处理输入输出的类,新IO采用内存映射文件的方式处理输入输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件,这种方式进行输入输出比传统的输入输出快的多

    NIO基础

    Channel通道和Buffer缓冲是NIO中的两个核心对象

    • Chanel是对传统输入输出系统的模拟,通过map方法可以将一块数据映射到内存中

    • Buffer本质是一个数组,发送到Channel中的所有对象都必须先放到Buffer中,而从Channel中读取的数据必须先放到Buffer中

    NIO和多路复用的区别

    IO模型

    • 同步阻塞IO(Blocking IO):即传统的IO模型

    • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库

    • 多路复用IO(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型

    • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

    java NIO就是采用多路复用IO模式。在多路复用IO模型中,会有一个线程(Java中的Selector)不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

    Buffer的使用

    Buffer是一个抽象类,主要作用是用于装入数据,然后输出数据

    • 最常见的子类ByteBuffer可以在底层字节数组上进行get/set操作

    • 其它基本数据类型都有对应的Buffer类:CharBuffer、 ShortBuffer、 IntBuffer、 LongBuffer、FloatBuffer、 DoubleBuffer

    静态方法static XxxBuffer allocate(int capacity)创建一个容量为capacity的XxxBuffer对象

    Buffer中有3个重要概念:容量capacity、界限limit和位置position

    • 容量capacity表示该Buffer的最大数据容量,创建后则不能改变

    • 界限limit,位于limit后的数据既不可被读,也不可被写

    • 位置position用于指明下一个可以被读出的或者写入缓冲区的位置索引

    • 标记mark位置

    Buffer的position为0,limit为capacity,程序可以通过put方法向Buffer写入一些数据,每放入一些数据,Buffer的position响应的向后移动。

    写文件操作

    String fileName = "nioFile"; 
    try (FileOutputStream fos = new FileOutputStream(new File(fileName))) { 
        FileChannel channel = fos.getChannel(); 
        ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("你好你好.."); 
        int length = 0; 
        while ((length = channel.write(byteBuffer)) != 0) { 
            System.out.println("写入长度:" + length); 
        } 
    } catch (IOException e) { 
        e.printStackTrace(); 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    读文件操作

    String fileName = "C:\IODemo\nioFile"; 
    try (FileInputStream fis = new FileInputStream(new File(fileName)); 
        FileChannel channel = fis.getChannel()) { 
        int capacity = 100; 
        ByteBuffer byteBuffer = ByteBuffer.allocate(capacity); 
        int length = -1; 
        while ((length = channel.read(byteBuffer)) != -1) { 
            byteBuffer.clear(); 
            byte[] array = byteBuffer.array(); 
            System.out.write(array, 0, length); 
            System.out.println(); 
        } 
    } catch (FileNotFoundException e) { 
    	e.printStackTrace(); 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Buffer类的常用方法
    • capacity():int返回Buffer的容量大小
    • hasRemaining():boolean判断是否还有元素可以进行处理
    • remaining():int返回当前位置和界限之间的元素个数
    • position():int返回当前操作的位置
    • mark():Buffer设置Buffer的标记位置,只能在0和position之间做标记
    • reset():Buffer将位置position转到mark所在的位置
    • rewind():Buffer将位置position设置到0,取消设置的mark
    • put(obj)用于向Buffer中放入数据
    • get()用于从Buffer中取出数据
    Channel类的常用方法

    Channel可以直接将文件的部分或者全部直接映射成Buffer

    注意:不能直接访问Channel中的数据,包括读取、写入都不行。Channel只能与Buffer进行交互

    • 所有Channel不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel方法来返回对应的Channel
    • 常用的是FileInputStream、FileOutputStream的getChannel()返回的FileChannel

    Channel中最常用的三个方法是map()、read()和write(),map()方法将Channel对应的部分或全部数据映射成ByteBuffer然后写入数据

    异常:java.nio.charset.MalformedInputException一般是编码转换时由于编码字符集错误导致的,可以修改Charset中编码字符集名称解决。例如GBK、UTF-8等

    selector

    selector是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel通道的状态是否处于可读、可写。如此可以实现单线程管理多个channels也就是可以管理多个网络链接。

    使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销

    • FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承SelectableChannel
    • 多用于网络应用编程中

    基本用法

    1. Selector的创建。通过调用Selector.open()方法创建一个Selector对象
    Selector selector = Selector.open(); 
    
    • 1
    1. 注册Channel到Selector
    channel.configureBlocking(false); 
    SelectionKey key = channel.register(selector, Selectionkey.OP_READ); 
    
    • 1
    • 2

    Channel必须是非阻塞的。

    1. 轮询方式获取选择器上的状态值
    while(selector.select()>0){ 
        Iterator<SelectionKey> it=selector.selectedKeys().iterator(); 
        ... 
    } 
    
    • 1
    • 2
    • 3
    • 4

    Chatset字符集

    所有文件在底层都是二进制文件,字符文件是系统将底层的二进制序列转换为字符,这里会涉及编码Encoder和解码Decoder

    • 将明文的字符序列转换为计算机理解的二进制序列称为编码
    • 将二进制序列转换为明文字符串称为解码

    Charset类

    • availableCharsets():SortedMap 获取当前JDK所支持的所有字符集
      • 字符串别名
        • GBK简体中文
        • ISO-8859-1拉丁文
        • UTF-8是8位UCS转换格式
    Charset c=Charset.forName("GBK"); 
    
    • 1

    Java7新增StandardCharsets类,其中包含了ISO_8859_1、UTF_8、UTF-16等类变量,这些类变量代表了最常见的字符集对应的Charset对象

    • newDecoder():CharsetDecoder获取该编码字符集对应的解码器
      • decode(ByteBuffer):CharBuffer方法可以将字节序列ByteBuffer转换为CharBuffer字符序列
    • newEncoder():CharsetEncoder获取该编码字符集对应的编码器
      • encode(CharBuffer):ByteBuffer方法可以将字符序列CharBuffer转换为ByteBuffer字节序列
    Charset c1 = Charset.forName("GBK"); 
    CharsetEncoder encoder = c1.newEncoder(); 
    CharsetDecoder decoder = c1.newDecoder(); 
    
    CharBuffer cb = CharBuffer.allocate(8); 
    cb.put('孙'); 
    cb.put('误'); 
    cb.put('空'); 
    cb.flip(); 
    ByteBuffer bb=encoder.encode(cb);//将CharBuffer转换为ByteBuffer 
    for(int i=0;i<6;i++) { 
    	System.out.println(bb.get(i)+" "); 
    }
    System.out.println("===================="); 
    System.out.println(decoder.decode(bb));//将byteBuffer转换为charBuffer 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    文件锁FileLock

    文件锁用于阻止多个进程并发修改同一个文件

    • 在NIO中提供了FileLock支持文件锁定功能
    • 在FileChannel中提供的lock()、tryLock()可以获取文件锁FileLock对象以锁定文件
      • lock()是用于锁定某个文件,如果无法获取文件锁,则程序一直阻塞等待
      • lock(long position, long size, boolean shared)对文件从position开始,长度为size的内容加锁,该方法是阻塞式的
      • tryLock()是尝试锁定文件,将直接返回而不是阻塞,如果获得文件锁则直接返回该文件锁,否则返回null
      • tryLock(long position, long size, boolean shared)非阻塞式加锁

    锁可以是排它锁或共享锁,当shared为true时表示共享锁,允许多个进程读取文件,但是阻止其它进程获取该文件的排它锁。默认是排它锁

    FileChannel channel=new FileOutputStream("a.txt").getChannel(); 
    FileLock lock=channel.tryLock();//非阻塞式对文件加锁 
    lock.release();//释放锁 
    
    • 1
    • 2
    • 3

    文件锁是建议性的,不是强制性的,就算一个程序不能获取文件锁,也可以对文件进行读写。文件锁是由Java虚拟机所持有的,关闭FileChannel时会释放Java虚拟机上该文件的所有锁

    AIO

    Jdk7中新增了一些与文件、网络I/O相关的一些api。这些API被称为NIO.2或称为AIO。AIO最大的一个特性就是异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。AIO是对JDK1.4中提出的同步非阻塞NIO的进一步增强。

    jdk7主要增加了三个新的异步通道:

    • AsynchronousFileChannel: 用于文件异步读写
    • AsynchronousSocketChannel: 客户端异步socket
    • AsynchronousServerSocketChannel: 服务器异步socket。

    因为AIO的实施需充分调用OS参与,IO需要操作系统支持、并发也同样需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。因此在实际中AIO使用不是很广泛

    异步无非是通知系统做一件事情,然后自己做其他事情去了。异步调用如何做后续处理。通常有两种方式:

    • 将来式: 当希望主线程发起异步调用,并轮询等待结果的时候使用将来式;
    • 回调式: 常说的异步回调就是它。

    将来式异步读取

    将来式用Java.util.concurrent声明一个Future,用来保存异步操作的处理结果。通常用Future get()方法在异步IO操作完成时获取其结果。

    AsynchronousFileChannel会关联线程池,它的任务是接收IO处理事件,并分发给负责处理通道中IO操作结果的结果处理器。跟通道中发起的IO操作关联的结果处理器确保是由线程池中的某个线程产生。

    Path path = Paths.get("data/a1.txt"); // 从语义的角度上说比File好一些,用于封装一 个文件或者文件夹对象 
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, 
    StandardOpenOption.READ); 
    ByteBuffer buffer = ByteBuffer.allocate(1024); 
    Future<Integer> future = channel.read(buffer, 0); 
    
    int readNum = future.get(); 
    buffer.flip(); 
    CharBuffer cb = CharBuffer.allocate(1024); 
    CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); 
    decoder.decode(buffer,cb,false); 
    cb.flip(); 
    
    String ss=new String(cb.array(),0,cb.limit()); 
    System.out.println(ss); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    回调式异步读取

    回调式所采用的事件处理技术,基本思想是主线程会派一个CompletionHandler到独立的线程中执行IO操作。这个对象将带着IO的操作的结果返回到主线程中,这个结果会触发它自己的completed或failed方法。

    在异步IO活动结束后,接口java.nio.channels.CompletionHandler会被调用,其中V是结果类型,A是提供结果的附着对象。此时必须已经有了该接口completed(V,A)和failed(V,A)方法的实现,这样程序才能知道异步IO操作成功或失败时该如何处理。

    Path path=Paths.get("/data/abc.txt"); 
    AsynchronousFileChannel channel=AsynchronousFileChannel.open(path); 
    ByteBuffer buffer=ByteBuffer.allocate(1024); 
    channel.read(buffer,0,buffer,new CompletionHandler<Integer,ByteBuffer>(){ 
    public void completed(Integer result,ByteBuffer attachment){ 
        	System.out.println(Thread.currentThread().getName()+" read success!"); 
        }
        public void failed(Throwable ex, ByteBuffer attachment){ 
        	System.out.println("read error!"); 
        } 
    }); 
    while(true){ 
        System.out.println(Thread.currentThread.getName()+" sleep"); 
        Thread.sleep(1000); 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    BIO、NIO和AIO对比

    • Java BIO : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
    • Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
    • Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,

    场景分析:

    • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
    • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
    • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。

    一般来说I/O模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞IO。

    • 同步阻塞IO: 在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!
    • 同步非阻塞IO: 在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
    • 异步阻塞IO: 此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性!
    • 异步非阻塞IO: 在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。
  • 相关阅读:
    详解 Java 17 中新推出的密封类
    Vite和Webpack区别
    企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图
    MapTR v2文章研读
    久经沙场的程序员居然也被某鱼的假程序员骗了,程序员之间的信任应该是最高的,他一个人毁了这种信任感
    ElasticSearch Filter Query过滤查询
    程序装载:“640K内存”真的不够用么?
    Go持续改进与代码审查
    2023MathorCup高校数模挑战赛B题完整解题代码教程
    华为Mate 60系列发售,北斗卫星通信技术进一步深入大众消费市场
  • 原文地址:https://blog.csdn.net/miao_6426/article/details/127224149