IO流

88 阅读3分钟

IO流类型:

  • 流操作的颗粒度划分:
    • 字节流:以字节为单位,可以操作任何类型的数据,以inputStream和outputStream作为基类
    • 字符流:以字符为单位,只能操作字符数据,操作纯文本文件,以Reader和Writer作为基类
  • 流的方向划分:
    • 输入流
    • 输出流

Unix中五种IO模型

  • 阻塞IO:应用程序读取缓冲区数据时,当内核没有准备好数据时,应用程序处于等待状态
  • 非阻塞IO:当用户空间缓冲区没数据时,返回错误信息给线程。线程采用轮询的方式请求数据
  • IO多路复用:应用程序通过select函数,监控多个fd(程序打开的文件),当准备好数据后,再去请求数据。
    • select缺点:

      • 监听的IO最大连接数有限,在Linux系统上一般为1024。poll函数解决了连接数限制问题
      • select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)。epoll解决遍历问题,fd就绪时,内核采用回调机制激活fd,通过epoll_wait()得到通知
  • 信号驱动模型:程序调用sigaction,向内核发送Sigio信号,当内核准备好数据后,在通过Sigio通知应用
  • 异步模型:应用程序向内核发送请求后,立即返回。然后执行准备数据--->复制到用户空间,然后通知应用,数据准备好了

java中的NIO

结构概念图:

image.png

主要组件介绍:

  1. Channel(通道)
    • 类似于传统IO中的流,用于进行数据的读取和写入。通道可以是双向的,例如FileChannel用于文件IO,SocketChannel用于网络IO,等等。
  2. Buffer(缓冲区)
    • 缓冲区是NIO中用于临时存储数据的对象,可以在通道和应用程序之间传输数据。在读取数据时,数据会先被读入到缓冲区中,然后从缓冲区中进行读取;在写入数据时,数据会先被写入到缓冲区中,然后从缓冲区中写入到通道中。
  3. Selector(选择器)
    • 选择器是NIO中的核心组件,用于监视多个通道的事件状态,例如读、写、连接等事件。使用选择器可以避免使用多线程处理多个通道,从而提高了系统的性能和可伸缩性。

java代码操作实例:

    public static void main(String[] args) throws Exception {
        // 打开文件通道
        RandomAccessFile file = new RandomAccessFile("example.txt", "r");
        FileChannel fileChannel = file.getChannel();
        // 创建选择器
        Selector selector = Selector.open();
        // 注册通道到选择器,并指定感兴趣的事件为读事件
        fileChannel.register(selector, SelectionKey.OP_READ);
        // 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
            // 选择就绪的通道
            int readyChannels = selector.select();
            if (readyChannels == 0) {
                continue;
            }
            // 获取就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (key.isReadable()) {
                    // 从通道中读取数据到缓冲区
                    int bytesRead = fileChannel.read(buffer);
                    if (bytesRead == -1) {
                        // 读取完成,关闭通道
                        fileChannel.close();
                        return;
                    }
                    // 切换缓冲区到读模式
                    buffer.flip();
                    // 从缓冲区读取数据
                    while (buffer.hasRemaining()) {
                        System.out.print((char) buffer.get());
                    }
                    //清空缓冲区,准备下一次读取
                    buffer.clear();
                }
                // 移除当前的SelectionKey,因为已经处理过了
                keyIterator.remove();
            }
        }
    }
}


NIO和IO的区别:

  • 阻塞和非阻塞:
    • IO会等待内核准备好数据
    • NIO是读取数据到缓冲区中,如果内核没准备好数据,就会读取空数据到缓冲区中,是非阻塞的
  • 面向对象:
    • IO是面向流的,数据是在输入流或输出流中传输。流可以理解为内核和应用程序之间的管道
    • NIO面向缓冲区,通道中读取数据到缓冲区,或缓冲区中写入数据到通道
  • 方向:
    • IO是单向的,输入流或输出流只能进行读或写操作
    • NIO是双向的,能在一个通道进行读写操作
  • 选择器:
    • NIO可以通过选择器,一个线程管理多个通道,与内核进行交互 `