# Java进阶IO框架

# 1. Java IO分类体系

# 1.1 传输方向分类

Java IO按传输方向分为两大类:

输入流(Input):
从数据源(文件、网络、内存等)读取数据 → 程序
      [数据源] → [InputStream] → [程序]

输出流(Output):
从程序写入数据 → 目标位置
      [程序] → [OutputStream] → [目标位置]
1
2
3
4
5
6
7
8
9

# 1.2 操作数据类型分类

字节流(Byte Streams):
处理原始二进制数据,8位为单位
┌─────────────────────┐    ┌─────────────────────┐
│   InputStream       │    │   OutputStream      │
├─────────────────────┤    ├─────────────────────┤
│ FileInputStream     │    │ FileOutputStream    │
│ ByteArrayInputStream│    │ByteArrayOutputStream│
│ BufferedInputStream │    │BufferedOutputStream │
└─────────────────────┘    └─────────────────────┘

字符流(Character Streams):
处理文本数据,16位Unicode字符,自动处理编码
┌──────────────────┐    ┌──────────────────┐
│    Reader        │    │    Writer        │
├──────────────────┤    ├──────────────────┤
│  FileReader      │    │  FileWriter      │
│ BufferedReader   │    │ BufferedWriter   │
│ InputStreamReader│    │OutputStreamWriter│
└──────────────────┘    └──────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 1.3 设计模式:装饰者模式

Java IO大量使用装饰者模式,动态扩展功能:

基础组件:InputStream、OutputStream、Reader、Writer
装饰器:BufferedInputStream、DataInputStream等

使用示例:
普通文件流:只能读字节
FileInputStream fis = new FileInputStream("test.txt");

添加缓冲功能:提高读取效率
BufferedInputStream bis = new BufferedInputStream(fis);

再添加数据读取功能:可以读基本类型
DataInputStream dis = new DataInputStream(bis);

结构示意:
      [DataInputStream]
            ↑ 装饰
   [BufferedInputStream]
            ↑ 装饰
   [FileInputStream]

每层装饰都扩展了功能:
FileInputStream:提供基本文件读取能力
BufferedInputStream:添加缓冲区,减少IO次数
DataInputStream:添加读取Java基本数据类型能力
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

# 2. 源码深度解析

# 2.1 InputStream源码核心

// InputStream抽象类结构
public abstract class InputStream implements Closeable {
    
    // 核心方法1:读取一个字节(抽象方法)
    public abstract int read() throws IOException;
    
    // 核心方法2:读取到字节数组
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    
    // 核心方法3:读取到字节数组的指定部分
    public int read(byte b[], int off, int len) throws IOException {
        // 参数检查...
        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;
        
        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
            // 异常处理...
        }
        return i;
    }
    
    // 核心方法4:跳过指定字节数
    public long skip(long n) throws IOException {
        // 实现略...
    }
    
    // 核心方法5:返回可读取字节数的估计值
    public int available() throws IOException {
        return 0;
    }
    
    // 核心方法6:关闭流
    public void close() throws IOException {}
    
    // 核心方法7:标记/重置位置(需要子类支持)
    public synchronized void mark(int readlimit) {}
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
    public boolean markSupported() {
        return false;
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 2.2 OutputStream源码核心

public abstract class OutputStream implements Closeable, Flushable {
    
    // 核心方法1:写入一个字节(抽象方法)
    public abstract void write(int b) throws IOException;
    
    // 核心方法2:写入字节数组
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }
    
    // 核心方法3:写入字节数组的指定部分
    public void write(byte b[], int off, int len) throws IOException {
        // 参数检查...
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }
    
    // 核心方法4:刷新缓冲区
    public void flush() throws IOException {}
    
    // 核心方法5:关闭流
    public void close() throws IOException {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.3 装饰者模式在IO中的实现

以BufferedInputStream为例,分析装饰者模式实现:

类结构关系:
BufferedInputStream extends FilterInputStream
FilterInputStream extends InputStream

数据流向示意:
程序请求数据 → BufferedInputStream → 内部缓冲区 → 底层InputStream → 实际数据源

缓冲区工作原理:
初始状态:缓冲区空,position=0
┌───┬───┬───┬───┬───┬───┬───┬───┐
│   │   │   │   │   │   │   │   │ ← 8192字节缓冲区
└───┴───┴───┴───┴───┴───┴───┴───┘
↑
position=0

第一次read()调用:填充缓冲区
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │   │   │ ← 从底层流读取6个字节
└───┴───┴───┴───┴───┴───┴───┴───┘
↑
position=0, count=6

读取3个字节后:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┘
         ↑
      position=3

当position==count时,下次读取会触发重新填充
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

# 3. 常见IO类使用示例

# 3.1 文件读写操作

// 1. 传统字节流文件复制
try (FileInputStream fis = new FileInputStream("source.txt");
     FileOutputStream fos = new FileOutputStream("target.txt")) {
    
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        fos.write(buffer, 0, bytesRead);
    }
}

// 2. 缓冲流提高性能(装饰者模式应用)
try (BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("source.txt"));
     BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("target.txt"))) {
    
    int data;
    while ((data = bis.read()) != -1) {
        bos.write(data);
    }
}

// 3. 字符流处理文本(自动处理编码)
try (BufferedReader reader = new BufferedReader(
        new FileReader("source.txt"));
     BufferedWriter writer = new BufferedWriter(
        new FileWriter("target.txt"))) {
    
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
}

// 4. 使用NIO文件操作(Java 7+)
Path sourcePath = Paths.get("source.txt");
Path targetPath = Paths.get("target.txt");
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
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

# 3.2 字节流与字符流转换

编码转换过程示意:

字节流 → 字节数组 → 根据字符集解码 → 字符
字符 → 根据字符集编码 → 字节数组 → 字节流

示例:UTF-8文件读取
文件内容(UTF-8编码):"你好" = [0xE4 0xBD 0xA0, 0xE5 0xA5 0xBD]
读取过程:
1. InputStreamReader读取字节流
2. 使用UTF-8解码器将字节转换为字符
3. 得到字符:'你' (U+4F60) 和 '好' (U+597D)
1
2
3
4
5
6
7
8
9
10
11
// 指定字符集读取文件
try (InputStreamReader isr = new InputStreamReader(
        new FileInputStream("source.txt"), "UTF-8");
     BufferedReader br = new BufferedReader(isr)) {
    
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}
1
2
3
4
5
6
7
8
9
10

# 4. Unix IO模型

# 4.1 五种IO模型对比

Unix/Linux系统提供5种IO模型:

1. 阻塞IO(Blocking IO):
   用户进程调用recvfrom → 内核准备数据 → 数据就绪 → 复制到用户空间 → 返回
   ┌─────────┐    调用recvfrom    ┌─────────┐
   │用户进程  │───────────────────→│  内核   │
   │         │←───────────────────│         │
   │ 阻塞等待 │  数据未就绪,阻塞     │准备数据 │
   │         │←───────────────────│         │
   │         │  数据就绪,返回       │复制数据 │
   └─────────┘                    └─────────┘

2. 非阻塞IO(Non-blocking IO):
   用户进程轮询调用recvfrom,立即返回结果
   ┌─────────┐    调用recvfrom     ┌─────────┐
   │用户进程 │───────────────────→│  内核   │
   │         │←─ EWOULDBLOCK ────│数据未就绪│
   │         │    再次调用        │         │
   │         │───────────────────→│         │
   │         │←─── 成功返回 ─────│数据已就绪│
   └─────────┘                    └─────────┘

3. IO多路复用(IO Multiplexing):
   使用select/poll/epoll监控多个socket
   ┌─────────┐    调用select      ┌─────────┐
   │用户进程 │───────────────────→│  内核   │
   │         │←── 无就绪事件 ────│监控多个fd│
   │         │    再次调用        │         │
   │         │───────────────────→│         │
   │         │←── 有就绪事件 ────│  返回   │
   │         │调用recvfrom处理    │         │
   └─────────┘                    └─────────┘

4. 信号驱动IO(Signal-driven IO):
   内核在数据就绪时发送SIGIO信号
   ┌─────────┐    设置信号处理     ┌─────────┐
   │用户进程 │───────────────────→│  内核   │
   │         │←─ 立即返回 ───────│         │
   │ 继续执行 │    收到SIGIO信号   │数据就绪 │
   │         │←────────SIGIO─────│         │
   │         │调用recvfrom读取    │复制数据 │
   └─────────┘                    └─────────┘

5. 异步IO(Asynchronous IO):
   调用后立即返回,内核完成所有操作后通知
   ┌─────────┐   调用aio_read     ┌─────────┐
   │用户进程 │───────────────────→│  内核   │
   │         │←─ 立即返回 ───────│         │
   │ 继续执行 │                   │准备数据 │
   │         │                   │复制数据 │
   │         │←─── 完成通知 ─────│  完成   │
   └─────────┘                    └─────────┘
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
41
42
43
44
45
46
47
48
49
50
51
52

# 5. Java BIO详解

# 5.1 BIO工作模型

BIO(Blocking IO)模型:

服务器端流程:
1. 创建ServerSocket,绑定端口
2. 调用accept()阻塞等待客户端连接
3. 为每个连接创建新线程处理

示意图:
          ┌─────────┐
          │ServerSocket│
          │  accept()  │
          └─────┬─────┘
                │阻塞等待连接
          ┌─────▼─────┐
          │Client连接 │
          └─────┬─────┘
          ┌─────▼─────┐
          │创建新线程 │→┐
          │处理该连接 │ │
          └───────────┘ │
          ┌───────────┐ │
          │创建新线程 │←┘
          │处理该连接 │
          └───────────┘

客户端连接:
客户端1 ──────┐
              ├──→ 线程1
客户端2 ──────┤
              ├──→ 线程2
客户端3 ──────┤
              ├──→ 线程3
...          │
              └──→ 线程N

问题:每个连接一个线程,资源消耗大,并发连接数有限。
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

# 5.2 BIO代码示例

// 服务器端代码
public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("BIO服务器启动,端口8080");
        
        while (true) {
            // 1. 阻塞等待客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("接收到客户端连接: " + socket.getInetAddress());
            
            // 2. 为每个连接创建新线程处理
            new Thread(() -> {
                try {
                    // 3. 获取输入输出流
                    BufferedReader reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                    PrintWriter writer = new PrintWriter(
                        socket.getOutputStream(), true);
                    
                    String request;
                    // 4. 读取客户端请求(阻塞)
                    while ((request = reader.readLine()) != null) {
                        System.out.println("收到请求: " + request);
                        
                        // 5. 处理请求并返回响应
                        String response = "服务器响应: " + request;
                        writer.println(response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

// 客户端代码
public class BioClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8080);
        
        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));
        
        // 发送请求
        writer.println("Hello Server!");
        
        // 接收响应(阻塞)
        String response = reader.readLine();
        System.out.println("收到响应: " + response);
        
        socket.close();
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 6. Java NIO基础详解

# 6.1 NIO核心组件

NIO(Non-blocking IO)三大核心组件:

1. Channel(通道):双向数据传输管道
   ┌─────────────┐     数据流      ┌─────────────┐
   │    Channel  │←──────────────→│    Buffer   │
   └─────────────┘                └─────────────┘
   类型:FileChannel, SocketChannel, ServerSocketChannel, DatagramChannel

2. Buffer(缓冲区):数据临时存储区
   Buffer内部结构:
   ┌───┬───┬───┬───┬───┬───┬───┬───┐
   │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ ← 底层字节数组
   └───┴───┴───┴───┴───┴───┴───┴───┘
   ↑       ↑           ↑           ↑
   mark  position    limit      capacity
   
   操作过程示例:
   初始状态:position=0, limit=capacity=8
   ┌───┬───┬───┬───┬───┬───┬───┬───┐
   │   │   │   │   │   │   │   │   │
   └───┴───┴───┴───┴───┴───┴───┴───┘
    ↑                               ↑
 position=0                     limit=8
   
   写入4个字节后:
   ┌───┬───┬───┬───┬───┬───┬───┬───┐
   │ A │ B │ C │ D │   │   │   │   │
   └───┴───┴───┴───┴───┴───┴───┴───┘
                ↑           ↑
           position=4    limit=8
   
   flip()切换为读模式:
   ┌───┬───┬───┬───┬───┬───┬───┬───┐
   │ A │ B │ C │ D │   │   │   │   │
   └───┴───┴───┴───┴───┴───┴───┴───┘
    ↑           ↑
 position=0  limit=4
   
   读取2个字节后:
   ┌───┬───┬───┬───┬───┬───┬───┬───┐
   │ A │ B │ C │ D │   │   │   │   │
   └───┴───┴───┴───┴───┴───┴───┴───┘
        ↑       ↑
 position=2  limit=4

3. Selector(选择器):多路复用器
   单线程管理多个Channel
   ┌─────────┐     注册     ┌─────────┐
   │ Selector│←───────────→│ Channel │
   │         │   事件通知   │         │
   │         │────────────→│         │
   └─────────┘             └─────────┘
          ↑
          │ 轮询select()
   ┌─────────┐
   │   线程   │
   └─────────┘
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 6.2 NIO非阻塞服务器示例

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建Selector
        Selector selector = Selector.open();
        
        // 2. 创建ServerSocketChannel并配置为非阻塞
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        
        // 3. 注册到Selector,监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO服务器启动,端口8080");
        
        while (true) {
            // 4. 轮询就绪的Channel(非阻塞,可设置超时)
            if (selector.select(1000) == 0) {
                System.out.println("等待1秒,无连接...");
                continue;
            }
            
            // 5. 获取就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                
                if (key.isAcceptable()) {
                    // 6. 处理ACCEPT事件
                    handleAccept(key, selector);
                } else if (key.isReadable()) {
                    // 7. 处理READ事件
                    handleRead(key);
                } else if (key.isWritable()) {
                    // 8. 处理WRITE事件
                    handleWrite(key);
                }
                
                keyIterator.remove(); // 处理完后移除
            }
        }
    }
    
    private static void handleAccept(SelectionKey key, Selector selector) 
            throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverChannel.accept();
        socketChannel.configureBlocking(false);
        
        // 注册READ事件,准备读取数据
        socketChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("接受新连接: " + socketChannel.getRemoteAddress());
    }
    
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        int bytesRead = channel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip(); // 切换为读模式
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String message = new String(data);
            System.out.println("收到消息: " + message);
            
            // 注册WRITE事件,准备写响应
            channel.register(key.selector(), SelectionKey.OP_WRITE, 
                           "响应: " + message);
        } else if (bytesRead == -1) {
            channel.close();
        }
    }
    
    private static void handleWrite(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        String response = (String) key.attachment();
        
        ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
        channel.write(buffer);
        
        // 写完后重新注册READ事件
        channel.register(key.selector(), SelectionKey.OP_READ);
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

# 7. Java NIO多路复用详解

# 7.1 Selector工作原理

Selector多路复用模型:

单个Selector线程管理多个Channel:
┌─────────────────────────────────┐
│           Selector线程          │
├──────────┬──────────┬──────────┤
│ 轮询select() │  处理ACCEPT  │  处理READ   │
└──────────┴──────────┴──────────┘
         ↓           ↓           ↓
┌──────────┐  ┌──────────┐  ┌──────────┐
│ Channel1 │  │ Channel2 │  │ Channel3 │
│ ACCEPT就绪│  │ READ就绪  │  │ WRITE就绪 │
└──────────┘  └──────────┘  └──────────┘

Selector内部数据结构:
注册的Channel集合:
┌─────────────────────────────────────┐
│        Selector维护的键集合          │
├─────────────────────────────────────┤
│ SelectionKey1 → Channel1 + Interest │
│ SelectionKey2 → Channel2 + Interest │
│ SelectionKey3 → Channel3 + Interest │
└─────────────────────────────────────┘

select()过程:
1. 检查注册的Channel,哪些有就绪事件
2. 将就绪的Channel添加到selectedKeys集合
3. 返回就绪的Channel数量

事件类型:
OP_ACCEPT: 接受连接就绪
OP_CONNECT: 连接建立就绪  
OP_READ: 读就绪(有数据可读)
OP_WRITE: 写就绪(可写入数据)
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

# 7.2 select/poll/epoll对比

Linux下IO多路复用的三种实现:

1. select模型:
   数据结构:fd_set(位图),大小固定(通常1024)
   工作流程:
   用户空间 ──→ 内核空间
       复制fd_set → 遍历所有fd → 有事件则标记
       修改后的fd_set ← 返回
   
   缺点:
   - fd数量限制(1024)
   - 每次需要复制整个fd_set
   - 线性扫描所有fd,效率低

2. poll模型:
   数据结构:pollfd数组,无数量限制
   工作流程与select类似,但无数量限制
   
   缺点:
   - 每次需要复制整个pollfd数组
   - 线性扫描,连接越多性能越差

3. epoll模型(Linux特有):
   数据结构:红黑树 + 双向链表
   工作流程:
   初始:epoll_create()创建epoll实例
   注册:epoll_ctl()添加fd到红黑树
   等待:epoll_wait()从就绪链表获取事件
   
   优点:
   - 无fd数量限制
   - 事件驱动,只返回就绪的fd
   - 内存映射,减少数据拷贝
   
   epoll内部结构:
   ┌─────────────────┐
   │   epoll实例     │
   ├─────────────────┤
   │ 红黑树(存储所有fd)│
   │    ↑            │
   │    │ 事件就绪    │
   │    ↓            │
   │ 双向链表(就绪fd)│
   └─────────────────┘
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
41
42
43
44

# 7.3 Java NIO与epoll

Java在不同平台使用不同的Selector实现:

Linux: 使用epoll
Windows: 使用select或IOCP
MacOS: 使用kqueue

代码示例:查看Selector实现类
public class SelectorProviderExample {
    public static void main(String[] args) {
        SelectorProvider provider = SelectorProvider.provider();
        System.out.println("Selector提供者: " + provider.getClass());
        
        Selector selector = provider.openSelector();
        System.out.println("Selector实现: " + selector.getClass());
    }
}

// Linux下输出可能:
// Selector提供者: sun.nio.ch.EPollSelectorProvider
// Selector实现: sun.nio.ch.EPollSelectorImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8. Java AIO详解

# 8.1 AIO异步IO模型

AIO(Asynchronous IO)工作模型:

异步非阻塞,回调机制
┌─────────────────┐   1.发起异步操作   ┌─────────────────┐
│   用户程序      │─────────────────→│      内核       │
│                 │←── 2.立即返回 ────│                 │
│   继续执行      │                   │   处理IO操作    │
│   其他任务      │                   │                 │
│                 │←── 3.完成回调 ────│  操作完成,通知  │
└─────────────────┘                   └─────────────────┘

与NIO对比:
NIO(同步非阻塞):轮询检查IO状态,状态就绪后需要同步读写
AIO(异步非阻塞):发起IO后立即返回,内核完成后主动通知

AIO核心类:
AsynchronousSocketChannel: 异步Socket通道
AsynchronousServerSocketChannel: 异步服务器Socket通道
CompletionHandler: 完成回调接口
Future: 异步操作结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8.2 AIO服务器示例

public class AioServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 1. 创建异步服务器Socket通道
        AsynchronousServerSocketChannel serverChannel = 
            AsynchronousServerSocketChannel.open();
        
        // 2. 绑定端口
        serverChannel.bind(new InetSocketAddress(8080));
        System.out.println("AIO服务器启动,端口8080");
        
        // 3. 异步接受连接(使用CompletionHandler回调)
        serverChannel.accept(null, new CompletionHandler<
            AsynchronousSocketChannel, Void>() {
            
            @Override
            public void completed(AsynchronousSocketChannel clientChannel, 
                                 Void attachment) {
                try {
                    System.out.println("接受连接: " + 
                        clientChannel.getRemoteAddress());
                    
                    // 4. 继续接受下一个连接(重要!)
                    serverChannel.accept(null, this);
                    
                    // 5. 处理客户端连接
                    handleClient(clientChannel);
                    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            @Override
            public void failed(Throwable exc, Void attachment) {
                System.err.println("接受连接失败: " + exc.getMessage());
                // 继续接受连接
                serverChannel.accept(null, this);
            }
        });
        
        // 6. 主线程保持运行
        Thread.currentThread().join();
    }
    
    private static void handleClient(AsynchronousSocketChannel clientChannel) {
        // 1. 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        // 2. 异步读取数据
        clientChannel.read(buffer, buffer, new CompletionHandler<
            Integer, ByteBuffer>() {
            
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                if (result > 0) {
                    attachment.flip();
                    byte[] data = new byte[attachment.remaining()];
                    attachment.get(data);
                    String message = new String(data);
                    System.out.println("收到消息: " + message);
                    
                    // 3. 异步写回响应
                    String response = "服务器响应: " + message;
                    ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
                    
                    clientChannel.write(writeBuffer, writeBuffer, 
                        new CompletionHandler<Integer, ByteBuffer>() {
                            @Override
                            public void completed(Integer result, ByteBuffer buffer) {
                                if (buffer.hasRemaining()) {
                                    // 如果还有数据未写完,继续写
                                    clientChannel.write(buffer, buffer, this);
                                } else {
                                    // 写完后继续读
                                    buffer.clear();
                                    clientChannel.read(buffer, buffer, this);
                                }
                            }
                            
                            @Override
                            public void failed(Throwable exc, ByteBuffer buffer) {
                                try {
                                    clientChannel.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                } else if (result == -1) {
                    try {
                        clientChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 继续读
                    attachment.clear();
                    clientChannel.read(attachment, attachment, this);
                }
            }
            
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    clientChannel.close();
                } 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

# 8.3 AIO使用Future模式

public class AioWithFuture {
    public static void main(String[] args) throws Exception {
        // 使用Future模式进行异步操作
        
        // 1. 创建异步Socket通道
        AsynchronousSocketChannel clientChannel = 
            AsynchronousSocketChannel.open();
        
        // 2. 异步连接(返回Future)
        Future<Void> connectFuture = clientChannel.connect(
            new InetSocketAddress("localhost", 8080));
        
        // 3. 可以继续执行其他任务...
        System.out.println("正在连接服务器,可以执行其他任务...");
        
        // 4. 等待连接完成(阻塞)
        connectFuture.get();
        System.out.println("连接成功");
        
        // 5. 异步写入数据
        String message = "Hello Server!";
        ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
        Future<Integer> writeFuture = clientChannel.write(writeBuffer);
        
        // 6. 可以执行其他任务...
        
        // 7. 等待写入完成
        writeFuture.get();
        System.out.println("数据发送完成");
        
        // 8. 异步读取响应
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        Future<Integer> readFuture = clientChannel.read(readBuffer);
        
        // 9. 等待读取完成
        readFuture.get();
        
        // 10. 处理响应
        readBuffer.flip();
        byte[] data = new byte[readBuffer.remaining()];
        readBuffer.get(data);
        String response = new String(data);
        System.out.println("收到响应: " + response);
        
        clientChannel.close();
    }
}
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
41
42
43
44
45
46
47

# 9. Netty框架详解

# 9.1 Netty架构设计

Netty核心组件架构:

┌─────────────────────────────────────────┐
│            ChannelPipeline              │
├──────────────┬──────────────┬──────────┤
│ ChannelHandlerContext      │         │
│   ↑          │   ↑          │   ↑     │
│ ChannelInboundHandler    │         │
│ ChannelOutboundHandler   │         │
└──────────────┴──────────────┴──────────┘
                ↓
┌─────────────────────────────────────────┐
│              EventLoopGroup              │
├──────┬──────┬──────┬──────┬──────┬──────┤
│EventLoop│EventLoop│EventLoop│EventLoop│ ... │
│ 线程1  │ 线程2  │ 线程3  │ 线程4  │      │
└──────┴──────┴──────┴──────┴──────┴──────┘
                ↓
┌─────────────────────────────────────────┐
│                 Channel                  │
│      (Socket连接抽象)                     │
└─────────────────────────────────────────┘

核心概念:
1. Channel: 网络连接抽象
2. EventLoop: 事件循环,处理IO事件
3. EventLoopGroup: EventLoop组,用于线程管理
4. ChannelHandler: 业务逻辑处理器
5. ChannelPipeline: 处理器链

线程模型:Reactor多线程模型
主从Reactor结构:
┌─────────────────┐     注册Channel    ┌─────────────────┐
│  BossGroup      │←─────────────────→│  WorkerGroup    │
│ (主Reactor)      │   分发连接        │ (从Reactor)      │
│ 1个EventLoop     │─────────────────→│ N个EventLoop     │
│ 处理accept事件    │                  │ 处理IO事件       │
└─────────────────┘                  └─────────────────┘
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

# 9.2 Netty服务器示例

// 1. 服务器端代码
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建两个EventLoopGroup
        // BossGroup:处理连接请求
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // WorkerGroup:处理IO操作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            // 创建服务器启动引导类
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class) // 使用NIO传输
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) throws Exception {
                             // 获取ChannelPipeline
                             ChannelPipeline pipeline = ch.pipeline();
                             
                             // 添加处理器(解码器 → 业务处理器 → 编码器)
                             pipeline.addLast(new StringDecoder());  // 字符串解码器
                             pipeline.addLast(new StringEncoder());  // 字符串编码器
                             pipeline.addLast(new ServerHandler());  // 业务处理器
                         }
                     })
                     .option(ChannelOption.SO_BACKLOG, 128)   // 连接队列大小
                     .childOption(ChannelOption.SO_KEEPALIVE, true); // 保持连接
            
            // 绑定端口并启动服务器
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("Netty服务器启动,端口8080");
            
            // 等待服务器通道关闭
            future.channel().closeFuture().sync();
        } finally {
            // 优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

// 2. 服务器业务处理器
public class ServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String message = (String) msg;
        System.out.println("服务器收到消息: " + message);
        
        // 回写响应
        String response = "服务器响应: " + message;
        ctx.writeAndFlush(response);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

// 3. 客户端代码
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                     .channel(NioSocketChannel.class)
                     .handler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) throws Exception {
                             ChannelPipeline pipeline = ch.pipeline();
                             pipeline.addLast(new StringDecoder());
                             pipeline.addLast(new StringEncoder());
                             pipeline.addLast(new ClientHandler());
                         }
                     });
            
            // 连接服务器
            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            
            // 发送消息
            Channel channel = future.channel();
            Scanner scanner = new Scanner(System.in);
            
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                channel.writeAndFlush(line);
            }
            
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

// 4. 客户端处理器
public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String response = (String) msg;
        System.out.println("客户端收到响应: " + response);
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

# 9.3 Netty核心特性

1. 零拷贝(Zero-copy):
   传统文件传输:
   文件 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡
   共4次拷贝,2次上下文切换
   
   Netty零拷贝:
   文件 → 内核缓冲区 → 网卡
   共2次拷贝,2次上下文切换
   
   实现方式:
   - FileRegion传输文件
   - CompositeByteBuf合并缓冲区
   - 直接内存(Direct Buffer)

2. 内存池(Memory Pool):
   避免频繁创建/销毁ByteBuf,提高内存使用效率
   ┌─────────────────┐
   │  内存池管理器    │
   ├─────────────────┤
   │   空闲内存块     │ → 分配 → ByteBuf
   │   已用内存块     │ ← 释放 ← ByteBuf
   └─────────────────┘

3. 高性能线程模型:
   一个EventLoop处理多个Channel
   ┌──────────────┐
   │  EventLoop   │
   │   (线程)     │
   ├──────────────┤
   │  Selector    │
   │    │         │
   │    ▼         │
   │  TaskQueue   │
   └──────────────┘
        ↑    ↑
        │    │
   ┌────┴────┴────┐
   │ Channel1 │ Channel2 │
   └──────────┴──────────┘
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

# 10. Java NIO零拷贝实现

# 10.1 传统IO与零拷贝对比

传统文件传输过程(4次拷贝,4次上下文切换):

1. 用户空间发起read()系统调用
   上下文切换:用户态 → 内核态
   
2. DMA从磁盘读取文件到内核缓冲区
   ┌─────────┐    DMA拷贝    ┌─────────────┐
   │   磁盘   │─────────────→│ 内核缓冲区   │
   └─────────┘              └─────────────┘

3. CPU将数据从内核缓冲区拷贝到用户缓冲区
   上下文切换:内核态 → 用户态
   ┌─────────────┐    CPU拷贝    ┌─────────────┐
   │ 内核缓冲区   │─────────────→│ 用户缓冲区   │
   └─────────────┘              └─────────────┘

4. 用户空间发起write()系统调用
   上下文切换:用户态 → 内核态
   
5. CPU将数据从用户缓冲区拷贝到Socket缓冲区
   ┌─────────────┐    CPU拷贝    ┌─────────────┐
   │ 用户缓冲区   │─────────────→│Socket缓冲区 │
   └─────────────┘              └─────────────┘

6. DMA将数据从Socket缓冲区发送到网卡
   上下文切换:内核态 → 用户态
   ┌─────────────┐    DMA拷贝    ┌─────────┐
   │Socket缓冲区 │─────────────→│  网卡   │
   └─────────────┘              └─────────┘

总计:4次拷贝 + 4次上下文切换

零拷贝过程(2次拷贝,2次上下文切换):

1. 用户空间发起sendfile()系统调用
   上下文切换:用户态 → 内核态
   
2. DMA从磁盘读取文件到内核缓冲区
   ┌─────────┐    DMA拷贝    ┌─────────────┐
   │   磁盘   │─────────────→│ 内核缓冲区   │
   └─────────┘              └─────────────┘

3. CPU将文件描述符信息拷贝到Socket缓冲区
   (只拷贝元数据,不拷贝实际数据)
   
4. DMA将数据从内核缓冲区直接发送到网卡
   ┌─────────────┐    DMA拷贝    ┌─────────┐
   │ 内核缓冲区   │─────────────→│  网卡   │
   └─────────────┘              └─────────┘
   上下文切换:内核态 → 用户态

总计:2次拷贝 + 2次上下文切换
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
41
42
43
44
45
46
47
48
49
50
51
52

# 10.2 Java零拷贝实现

// 1. 使用FileChannel.transferTo()实现零拷贝
public class ZeroCopyServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8080));
        
        while (true) {
            SocketChannel socketChannel = serverChannel.accept();
            
            // 传输文件
            File file = new File("largefile.dat");
            FileChannel fileChannel = new FileInputStream(file).getChannel();
            
            // 关键:使用transferTo实现零拷贝
            long position = 0;
            long size = fileChannel.size();
            
            while (position < size) {
                // transferTo内部使用sendfile系统调用
                long transferred = fileChannel.transferTo(position, 
                    size - position, socketChannel);
                position += transferred;
            }
            
            fileChannel.close();
            socketChannel.close();
        }
    }
}

// 2. 使用MappedByteBuffer内存映射文件
public class MappedFileExample {
    public static void main(String[] args) throws IOException {
        RandomAccessFile file = new RandomAccessFile("largefile.dat", "rw");
        FileChannel channel = file.getChannel();
        
        // 创建内存映射(将文件直接映射到内存)
        MappedByteBuffer mappedBuffer = channel.map(
            FileChannel.MapMode.READ_WRITE,  // 映射模式
            0,                               // 起始位置
            channel.size()                   // 映射大小
        );
        
        // 直接操作内存,无需系统调用
        byte[] data = new byte[1024];
        mappedBuffer.get(data);  // 读取数据
        
        // 修改数据
        mappedBuffer.put(0, (byte) 'X');
        
        // 数据会自动同步到文件
        mappedBuffer.force();
        
        channel.close();
        file.close();
    }
}

// 3. Netty中的零拷贝
public class NettyZeroCopyExample {
    public static void main(String[] args) {
        // Netty提供了多种零拷贝方式
        
        // 方式1:使用FileRegion
        File file = new File("largefile.dat");
        FileInputStream in = new FileInputStream(file);
        FileRegion region = new DefaultFileRegion(
            in.getChannel(), 0, file.length());
        
        // 在ChannelPipeline中传输
        channel.write(region);
        
        // 方式2:使用CompositeByteBuf合并缓冲区
        ByteBuf header = Unpooled.buffer(10);
        ByteBuf body = Unpooled.buffer(20);
        
        // 传统方式:需要拷贝
        // ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
        // allBuf.writeBytes(header);
        // allBuf.writeBytes(body);
        
        // 零拷贝方式:逻辑合并,不实际拷贝数据
        CompositeByteBuf compositeBuf = Unpooled.compositeBuffer();
        compositeBuf.addComponents(true, header, body);
        
        // 方式3:使用直接内存(Direct Buffer)
        // 堆内存:数据在JVM堆中,传输时需要拷贝到直接内存
        // 直接内存:数据直接在操作系统内存中,减少一次拷贝
        
        ByteBuf heapBuffer = Unpooled.buffer(1024);      // 堆内存
        ByteBuf directBuffer = Unpooled.directBuffer(1024); // 直接内存
        
        // 直接内存访问更快,但分配和释放成本更高
        // Netty使用内存池管理直接内存
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

# 10.3 零拷贝技术总结

Java中零拷贝的实现方式:

1. FileChannel.transferTo/transferFrom
   - 底层使用sendfile系统调用
   - 文件 → 网络传输场景
   - 完全零拷贝,无CPU参与数据拷贝

2. MappedByteBuffer内存映射
   - 底层使用mmap系统调用
   - 文件 ↔ 内存映射
   - 适合大文件随机访问

3. DirectByteBuffer直接内存
   - 内存不在JVM堆中,由操作系统管理
   - 减少JVM堆与本地内存的拷贝
   - Netty默认使用直接内存

4. Netty的CompositeByteBuf
   - 逻辑组合多个ByteBuf
   - 避免实际数据拷贝
   - 适合协议组装场景

性能对比:
┌─────────────────┬──────────┬──────────┬────────────┐
│    传输方式      │ 拷贝次数 │ CPU参与  │ 上下文切换 │
├─────────────────┼──────────┼──────────┼────────────┤
│   传统IO        │   4次    │   2次    │    4次     │
│   transferTo    │   2次    │   0次    │    2次     │
│   mmap          │   2次    │   1次    │    3次     │
└─────────────────┴──────────┴──────────┴────────────┘

使用建议:
- 大文件传输:使用transferTo
- 大文件随机访问:使用MappedByteBuffer  
- 网络编程:使用Netty + 直接内存
- 协议组装:使用CompositeByteBuf
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

# 总结

Java IO框架经历了从BIO到NIO再到AIO的演进,每种模型都有其适用场景:

  1. BIO:简单易用,适合连接数少、业务简单的场景
  2. NIO:高并发基础,适合连接数多、但活跃连接少的场景
  3. AIO:真正的异步,适合高性能要求场景
  4. Netty:生产级选择,提供完整的网络编程解决方案

零拷贝技术是高性能IO的关键,理解不同零拷贝技术的原理和适用场景,对于构建高性能系统至关重要。在实际开发中,应根据具体业务需求和技术栈选择合适的IO模型和优化策略。

最近更新: 2026/1/21
 |  Copyright © -Present Kelsen All Rights Reserved. | Author KelsenJing