# Java进阶IO框架
# 1. Java IO分类体系
# 1.1 传输方向分类
Java IO按传输方向分为两大类:
输入流(Input):
从数据源(文件、网络、内存等)读取数据 → 程序
[数据源] → [InputStream] → [程序]
输出流(Output):
从程序写入数据 → 目标位置
[程序] → [OutputStream] → [目标位置]
1
2
3
4
5
6
7
8
9
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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的演进,每种模型都有其适用场景:
- BIO:简单易用,适合连接数少、业务简单的场景
- NIO:高并发基础,适合连接数多、但活跃连接少的场景
- AIO:真正的异步,适合高性能要求场景
- Netty:生产级选择,提供完整的网络编程解决方案
零拷贝技术是高性能IO的关键,理解不同零拷贝技术的原理和适用场景,对于构建高性能系统至关重要。在实际开发中,应根据具体业务需求和技术栈选择合适的IO模型和优化策略。
← Java 进阶并发框架 Java进阶JVM →