• IO流 -- 调研


    前言

    对于io流其实一直在使用,但是其中原理不是很熟悉,至于能干什么,概念是比较熟悉的,但是深入了解还是欠缺,于是我开始整理这部门整体话知识,进行了一次调研;

    整体架构:
    在这里插入图片描述

    基础概念

    先说一下io流是什么整体概念

    Java的IO流是实现输入/输出的基础,在Java中把不同的输入/ 输出源抽象表述为"流"。流是一组有顺序的,有起点和终点的字节集合,流有输入和输出,输入时是流从数据源流向程序。输出时是流从程序传向数据源,而数据源可以是内存文件网络或程序等。

    能做什么: 数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类

    输入流和输出流

    输入流:只能从中读取数据,而不能向其写入数据
    输出流:只能向其写入数据,而不能从中读取数据
    在这里插入图片描述

    在这里插入图片描述

    字节流和字符流

    字节流和字符流和用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同。字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

    节点流和处理流

    可以向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被成为低级流。
    处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能,处理流也被称为
    高级流。

    //节点流,直接传入的参数是IO设备
    FileInputStream fis = new FileInputStream("test.txt"); 
    //处理流,直接传入的参数是流对象
    BufferedInputStream bis = new BufferedInputStream(fis);
    
    • 1
    • 2
    • 3
    • 4

    IO流实现框架 NIO

    先说一下NIO 是什么

    Java NIO(New I/O)是在Java 1.4版本中引入的新的输入输出API,用于替代传统的Java IO(java.io)API。Java NIO提供了更高级、更灵活的I/O操作方式,以应对日益增长的网络和并发需求。

    在Java 1.4之前,Java IO是使用阻塞式IO模型,即当一个IO操作发生时,程序会被阻塞,直到该操作完成。这种模型在处理大量并发连接时存在性能和可伸缩性的问题。

    为了解决这些问题,Java NIO引入了非阻塞式IO模型,通过使用通道(Channel)和缓冲区(Buffer)来进行数据的读取和写入。通道可以同时处理多个连接,而不需要为每个连接创建一个线程,从而大大提高了性能和可伸缩性。

    Java NIO还引入了选择器(Selector)的概念,用于实现多路复用,即同时监控多个通道的事件。选择器可以高效地检测到有事件发生的通道,并及时进行处理,而不需要通过轮询来判断通道的状态变化。

    Java NIO的引入使得Java在处理网络、并发和高性能应用方面有了更好的支持。它在大规模的网络服务、高并发的Web服务器、分布式系统等领域得到了广泛的应用。

    需要注意的是,虽然Java NIO在性能和可伸缩性方面带来了很大的改进,但它的编程模型和API相对复杂,对开发人员的要求也更高。因此,在使用Java NIO时需要仔细理解其概念和原理,并根据具体的需求和场景来进行合理的选择和使用。

    NIO的核心组件是通道(Channel)缓冲区(Buffer)。通道是对底层IO设备(如文件、套接字)的一个抽象,可以通过通道进行数据的读写操作。缓冲区是一个固定大小的内存块,可以在通道和应用程序之间传输数据。

    NIO的特点包括:

    1. 非阻塞IO:NIO可以使用单线程处理多个通道的IO操作,通过使用选择器(Selector)来监听多个通道的事件状态,从而实现非阻塞IO

    2. 内存映射文件:NIO提供了内存映射文件(MappedByteBuffer)的功能,可以将文件直接映射到内存中,避免了传统IO中频繁的数据复制操作。

    3. 选择器:选择器是NIO的核心组件之一,可以通过选择器来监听多个通道的事件状态,从而实现对多个通道的管理。

    总之,NIO提供了一种更高效、更灵活的IO方式,适用于处理大量并发连接和高速IO操作的场景。


    简单实现

    那么既然NIO是 JDK 1.4自带的那么我们简单实现一个,说一下前提

    使用Java NIO,首先需要了解以下几个核心概念和类:

    1. 通道(Channel):通道是Java NIO中用于读取和写入数据的抽象。可以通过FileChannel、SocketChannel、ServerSocketChannel等具体实现类来创建通道。

    2. 缓冲区(Buffer):缓冲区是Java NIO中用于存储数据的容器。可以使用ByteBuffer、CharBuffer等具体实现类创建不同类型的缓冲区。

    3. 选择器(Selector):选择器是Java NIO中用于多路复用非阻塞IO操作的工具。可以使用Selector类创建选择器,并注册通道到选择器上进行监控。

    下面是一个简单的示例,演示如何使用Java NIO进行文件读取:

    import java.io.FileInputStream;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class NIOExample {
        public static void main(String[] args) {
            try (FileInputStream fis = new FileInputStream("path/to/input/file");
    			// 获取通道
                 FileChannel channel = fis.getChannel()) {
                // 创建缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                // 从通道读取数据到缓冲区
                int bytesRead = channel.read(buffer);
                
                while (bytesRead != -1) {
                    System.out.println("Read " + bytesRead + " bytes");
                    
                    // 切换缓冲区为读模式
                    buffer.flip();
                    
                    // 从缓冲区读取数据
                    while (buffer.hasRemaining()) {
                        System.out.print((char) buffer.get());
                    }
                    
                    // 清空缓冲区,准备下次读取
                    buffer.clear();
                    
                    // 继续从通道读取数据到缓冲区
                    bytesRead = channel.read(buffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    以上示例中,首先创建一个文件输入流和文件通道,然后创建一个缓冲区。通过循环从通道读取数据到缓冲区,再从缓冲区读取数据进行处理。最后,清空缓冲区并继续读取,直到读取完所有数据。

    这只是一个简单的示例,Java NIO还有更多的功能和用法,如文件写入、网络通信等。

    对比简单的IO框架读写文件 感受一下区别:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    
    public class IOExample {
        public static void main(String[] args) {
            // 读取文件
            try (BufferedReader reader = new BufferedReader(new FileReader("path/to/input/file"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            // 写入文件
            try (BufferedWriter writer = new BufferedWriter(new FileWriter("path/to/output/file"))) {
                writer.write("Hello, World!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    以上示例中,通过使用BufferedReader和FileReader来读取文件内容,逐行打印到控制台。通过使用BufferedWriter和FileWriter来写入文件,将字符串"Hello, World!"写入文件。

    在使用Java IO读取和写入文件时,需要注意及时关闭文件流以释放资源。使用Java 7的try-with-resources语句可以方便地自动关闭文件流,无需显式调用close()方法。

    此外,还可以使用其他Java IO类来实现不同的读写操作,如InputStream、OutputStream、FileInputStream、FileOutputStream等,具体选择根据需求来决定。

    注意,Java IO是阻塞式IO模型,当进行IO操作时,程序会被阻塞,直到操作完成。如果需要非阻塞式IO操作,使用Java NIO。因为NIO是多路复用的网络模型架构,那么就涉及到NIO如何实现这种多路复用,其实在组件中已经有了头绪,就是选择器,通道,缓冲区

    如何实现的多路复用,同步非阻塞

    参考知乎上的答案

    • 同步与异步: 同步和异步关注的消息通信机制 synchronous communication / asynchronous communicaton。 所谓的同步就是在调用时候,在没有得到结果之前,该调用的结果不反悔,但是一旦调用了,就有返回值,换句话说,就是由调用者主动等待调用返回结果
    • 阻塞和非阻塞:阻塞和非阻塞关注的是程序在等待调用的结果例如消息,和返回值状态。阻塞调用是指调用结果返回之前,该线程会被挂起,调用线程只有得到结果之后才会返回回来,那么非阻塞调用是指调用在不能返回之前,这个通道没有阻塞当前进线程

    那么在知道这个概念之后,同步和异步,阻塞和非阻塞,说一下IO多路复用方案

    其实在开发中,一般I/O多路复用机制都是依赖于一个事件的多路复用分离器,可以这么理解,两个人聊天,找一个传话人就可以,由他去处理,这样来实现多路分离器。所以说分离器对象可以将来自事件源I/O 事件分离出来,并分发到对应的read/write 事件分离器中,作为开发人员预先注册需要处理的事件和事件处理器;那么通过事件处理器来负责将请求事件传递给事件处理器进行处理

    那么市面上有两种常用的事件分离模式,分别是reactor 和 proactor 。说一下这两种事件分离模式的区别,Reactor模式采用同步I/O 而Proactor 是采用异步I/O ,在Reactor模式中,事件分离器负责等待文件描述符或者socket作为读写准备操作就绪,然后将就绪的事件传递给对应的处理器,最后交由处理器来完成工作。

    而Proactor 模式,处理器或者事件分离器,只是负责发起异步读写操作,I/O操作本身由操作系统完成,传递给操作系统的参数,需要包括用户定义的缓冲区地址和数据大小,操作系统才能从中得到写出操作所需要的数据,或者写入从socket读到的数据,事件分离器捕获I/O操作完成事件,然后将事件传递给对应处理器,比如在windows上,处理器发起一个异步I/O操作,再由事件分离器等待IO。completion事件,典型的异步模式实现,都是建立在操作系统支持异步API基础之上完成的,我们将这种实现称为,系统级别异步或者 真 异步,因为应用程序完成全依赖操作系统来执行真正的I/O操作

    说的可能不能理解,举个例子

    在Reactor实现读

    • 注册读就绪是事件和对应的事件处理器
    • 事件分离器等待事件结果
    • 事件过来了,激活分离器,分离器调用事件对应的处理器
    • 事件处理器完成实际的读操作,处理完成读的数据,注册新的事件,然后返回控制权

    在Proactor实现的读

    • 操作系统支持异步APi的基础上,处理器发起异步读操作,处理器无视I/O是否就绪,它只关心是否完成了事件
    • 事件分离器等待操作完成事件
    • 分离器等待中,操作系统利用并行的内核线程执行实际的读操作,将数据结果数据存到用户自定义的缓冲区,最后通知事件分离器来进行读操作
    • 事件分离器调用事件处理器
    • 然后事件处理器处理用户自定义的缓冲区数据,启用异步的新线程操作
    • 最后把控制权交给分离器

    结论是:两个模式的共同点,都是对于某个I/O事件的事件通知,告诉某个模块,这个I/O操作可以进行,或者已经完成了,在结构上面,两个相同点和不同点,我罗列一下,方便更直观的看出一些东西

    相同点:处理器当提交I/O操作,判断设备是否可以操作异步io 当满足条件的情况下,都是由分离器交给处理器处理,然后交出控制权返回结果

    不同点,异步情况下,当调用处理器的时候,表示io已经完成了,不关注后面是否就绪,同步情况下Reactor 会关注是否就绪,可以进行操作

    IO流之传统BIO模型调研

    说一下概念: BIO就是同步阻塞模式的IO,一般两端交互,通常我们会通过 while 循环中服务端会调用accept方法去当代客户端的链接强求,那么我们常见的socket链接方式就是这种

    代码示例

  • 相关阅读:
    低代码和人工智能助力疫情期间抗原自测信息自动化收集和处理
    【React】精选5题
    Java:如何在PowerPoint幻灯片中创建散点图
    日常工作中程序员最讨厌哪些工作事项?
    CSS基础 2
    Xuperchain竞赛环境安装与工作环境搭建
    FastApi和Ajax传输图片
    Mockaroo - 在线生成测试用例利器
    我在这块牛X的A40i Linux开发板上点了个流水灯
    水表抄表系统:现代水资源管理的革新
  • 原文地址:https://blog.csdn.net/weixin_44550490/article/details/133806071