IO模型

  1. 阻塞IO (Blocking IO)

描述

  • 应用程序调用IO函数之后,会一直等待数据完成(如read()等)
  • 线程会被阻塞,直到数据从内核缓存区传到用户空间。

流程

  • 应用程序发起系统调用(如recv())
  • 内核等待数据并将数据拷贝到用户空间,注意这是操作系统内核要做的事情
  • 系统调用返回,解除阻塞

优点

  • 简单直观:模型简单,逻辑清晰
  • 成熟稳定:大多数系统的早期实现

缺点

  • 低效:线程在等待时无法去做其他工作
  • 浪费资源:每个IO操作需要一个独立线程或者进程,无法充分利用CPU资源

  1. 非阻塞IO(Non-Blocking IO)

描述

  • 应用程序在调用IO函数之后,即使数据尚未组装完成,函数也会立刻返回结果
  • 应用程序采用轮询的方式(polling)检查数据是否可用

流程

  • 应用程序发起系统调用(如recv())
  • 如果数据未准备好,立刻返回一个错误码(EWOULDBLOCK)
  • 应用程序需要重复调用以检查数据状态

优点

  • 线程不会被阻塞:换言之,线程可以去执行其他任务
  • 减少线程切换开销:避免阻塞导致的频繁的上下文切换

缺点

  • 轮询浪费资源:需要不断检查数据状态,效率低
  • 编程复杂:开发者需要实现轮询

  1. IO多路复用(IO Mutilplexing)

描述

  • 使单个线程同时监视多个IO描述符(文件,网络连接等)
  • 常用的系统调用有select(), poll(), epoll()

流程

  • 应用程序使用多路复用系统调用注册多个IO事件
  • 系统阻塞等待任一描述符就绪
  • 描述符就绪后,应用程序处理该IO操作

优点

  • 高效:一个线程管理多个IO操作,适合高并发的场景
  • 资源利用率高:避免为每个连接分配线程

缺点:

  • 复杂性增加:多路复用模型编程比较复杂
  • 性能瓶颈对部分实现如 select()):当描述符数量很大时,性能下降明显。

  1. 信号驱动IO(Sizgnal-driven IO)

描述:

  • 应用程序注册一个信号处理函数,当IO就绪时,操作系统会发送信号通知应用程序

流程:

  • 应用程序注册一个信号处理函数(如 SIGIO)。
  • 当 IO 就绪时,内核向应用程序发送信号。
  • 应用程序通过信号处理函数完成 IO 操作。

优点:

  • 非阻塞模式:线程无需轮询
  • 及时响应:信号触发机制减少等待时间

缺点

  • 信号处理复杂:信号的优先级、竞争条件处理难度高。
  • 移植性差:不同系统对信号机制的支持程度不一致。

  1. 异步IO(Asynchronous IO)

描述

  • 应用程序发起IO操作,立刻返回并继续执行其他任务
  • 当IO操作完成,操作系用通知应用程序

流程

  • 应用程序发起异步IO请求
  • 内核完成数据准备和数据传输后,通知应用程序(通过回调函数)
  • 应用程序处理已完成的数据

优点:

  • 高效:线程无阻塞或者轮询
  • 低延迟:内核和用户空间工作并行

缺点

  • 编程服装:需要处理异步回调
  • 移植性差:操作系统支持不一,可能影响跨平台兼容性
模型 阻塞 数据到达通知 线程占用 适用场景 性能
阻塞IO 每个线程阻塞 简单任务或者低并发场景 性能较低
非阻塞IO 多线程轮询 数据频繁到达的场景 性能一般
IO多路复用 是(短暂阻塞) 单线程管理多连接 高并发场景 性能较高
信号驱动IO 是(信号通知) 单线程异步响应 实时性要求高的场景 性能较高
异步IO 是(回调通知) 单线程异步完成 高性能并发或实时场景 性能最高

Java IO包

  1. IO的核心结构

JavaIO的基本概念是流(Stream),分为输入流和输出流,所谓的单向流

  • 输入流(InputStream/Reader):从数据源读取数据
  • 输出流(OutputStream/Reader):将数据写入目标
  1. 按处理类型分类

面向字节的IO

适用于二进制数据(图片,音频,视频文件)

核心类

  • InputStream:字节输入流的抽象类

    • 主线实现类:FileInputStream,ByteArrayInputStream,BufferedInputStream
  • OutputStream:字节输出流的抽象类

    • 主要实现类:FileOutputStream、ByteArrayOutputStream、BufferedOutputStream
try (FileInputStream fis = new FileInputStream("input".text)) {
    int data;
    while((data = fis.read())!= -1) {
        System.out.print((char) data);
    }
} catch(IOException e) {
}

面向字符的IO

适用于处理文本数据(字符流)

核心类

  • Reader:字符输入流的抽象类

    • 主要的实现类:FileReader、BufferedReader、InputStreamReader
  • Writer:字符输出流的抽象类

    • 主要实现类:FileWriter、BufferedWriter、OutputStreamWriter

适用场景

  • 适合处理文本文件,如.txt文件
try (BufferReader br = new BufferReader(new FileReader("input.txt")) {
    String line;
    while((line = br.getLine()) != null) {
        System.out.println(line);
    }
} catch (IOException) {
}
  1. 按功能增强分类

(1)缓冲流(Buffered Stream):通过添加缓冲区,提高读写效率

  • 字节缓冲流:BufferedInputStream,BufferedOutputStream
  • 字符缓冲流:BufferedReader、BufferedWriter

(2)转换流:在字节和字符串之间转换

  • InputStreamReader:将字节输入流转化为字符输入流
  • OutputStreamWriter:将字节输出流转化为字符输出流
try(InputStreamReader isr = new InputStreamBuffer(new FileInputStream("input.txt"),"UTF-8");
    BufferedReader br = new BufferedReader(isr)) {
    String line;
    while ((line = br.read()) != null) {
        System.out.println(line);
    }
}

(3) 数据流:用于读写Java的基本数据类型和字符串

  • DataInputStream:读取基本数据类型
  • DataOutputStream:写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
    dos.writeInt(123);
    dos.writeUTF("Hello")
} catch (IOException) {
}

try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
    int number = dis.readInt();
    String text = dis.readUTF();
} catch (IOException) {
}

(4) 对象流:用于读写对象(需要实现 Serializable 接口)

  • ObjectInputStream:读取对象
  • ObjectOutputStream:写入对象
class Person implement Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

try (ObjectOutputStream oos = new ObjectOutStream(new FileOutputStream("person.dat"))){
    oos.writeObject(new Person("Alice",25));
} catch (IOException) {
}

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))){
    Person p = (Person) ois.readObject();
} catch (IOException) {
}

(5) 文件流:用于直接操作文件

  • FileInputStream/FileOutputStream: 字节流文件操作
  • FileReader / FileWriter:字符流文件操作
  1. 按访问方式分类

(1)顺序流

按照数据的顺序从头到尾读取或写入,例如 FileInputStream。

(2)随机访问流

通过 RandomAccessFile 支持文件的随机读写操作。

try (RandomAccessFile ref = new RandomAccessFile("text.txt","rw")) {
    raf.seek(10); // 定位到第10个字节
    raf.write("Hello".getBytes());
} catch(IOException e) {
}

Java NIO包

Java NIO是Java在1.4版本引入的一组用于高性能IO操作的API,旨在解决传统Java IO的一些性能瓶颈。NIO提供了更加高效的、非阻塞的IO操作能力,特别适用于高并发,大规模数据传输的场景

  1. NIO的核心概念

(1)通道(Channel)

  • 通道是双向的,同时可以执行读和写操作
  • 类似于流,但比流的效率高
  • 核心接口的实现类:

    • FileChannel:用于文件数据的读写
    • SocketChannel:用于网络数据的读写
    • DatagramChannel:用于UDP的数据传输
    • ServerSocketChannel:用于服务端的网络数据连接监听

(2)缓冲区(Buffer)

  • 缓冲区是NIO数据读写的核心,数据总是从通道写入缓冲区,或者从缓冲区读取
  • 常用的缓冲区类型:

    • ByteBuffer(最常用):处理字节
    • CharBuffer:处理字符
    • IntBuffer,LongBuffer,DoubleBuffer等:处理对应的数据类型
  • 缓冲区的核心属性

    • capacity: 缓冲区的容量
    • position: 当前的读写位置
    • limit:缓冲区的操作范围上限
    • filp():切换读写模式
    • clear():重置缓冲区,准备写入数据

(3)多路复用器(Selector)

  • Selector是NIO的核心,用于管理多个通道
  • 它允许一个线程监控多个通道上的事件(如读、写、连接就绪等),实现非阻塞IO操作
  • 支持的事件:

    • OP_READ:通道可读
    • OP_WRITE:通道可写
    • OP_CONNECT:通道连接完成
    • OP_ACCEPT: 通道接受连接

IO和NIO区别

特性 IO NIO
数据处理模式 面向流 面向缓冲区
阻塞于非阻塞 阻塞IO 支持非阻塞IO
多路复用 不支持 支持Selector实现多路复用
数据访问方式 每次读写调用系统 批量数据可读,效率更高
线程模型 每个连接一个线程 一个线程可以管理多个连接

NIO的主要组件和实现

(1) 通道的使用

文件通道(FileChannel):用于文件的读写操作

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelExample {

    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("example.txt", "rw");
             FileChannel fileChannel = file.getChannel()) {

            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int bytesRead = fileChannel.read(buffer);
            while (bytesRead != -1) {
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                buffer.clear();
                bytesRead = fileChannel.read(buffer);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

网络通道(SocketChannel和ServerSocketChannel):用于处理网络数据

客户端连接

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class SocketChannelExample {

    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open()) {
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            buffer.put("Hello Server".getBytes());
            buffer.flip();
            socketChannel.write(buffer);

            buffer.clear();
            socketChannel.read(buffer);
            buffer.flip();
            System.out.println("Received: " + new String(buffer.array(), 0, buffer.limit()));

        } catch (IOException e) {
        }
    }
}

(2) 缓冲区的使用

核心方法

写入数据到缓冲区

  • put(byte b): 写入一个字节
  • put(byte[] src):写入一个字节数组

从缓冲区读取数据

  • get(): 读取一个字节
  • get(byte[] dst): 读取一个字节数组

状态切换

  • filp(): 从写切换到读
  • clear(): 清空缓冲区,准备重新写入
public class ByteBufferExample {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("Hello World".getBytes());

        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            System.out.print((char) byteBuffer.get());
        }
        byteBuffer.flip();
    }
}

(3) Selector的使用

示例:Selector处理多通道事件

public class SelectorExample {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();

        // 创建服务器通道并注册到 Selector
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(8080));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("Server is listening on port 8080...");

        while (true) {
            selector.select(); // 阻塞直到有事件发生
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();

                if (key.isAcceptable()) {
                    // 接受新的客户端连接
                    SocketChannel client = serverChannel.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 读取客户端数据
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = client.read(buffer);

                    if (bytesRead == -1) {
                        client.close();
                    } else {
                        buffer.flip();
                        client.write(buffer); // 回显数据
                    }
                }
            }
        }
    }
}

爱跑步的猕猴桃
1 声望0 粉丝