一、知识点梳理

一、Java I/O 概述

Java I/O 主要分为以下几种模型:

  1. BIO (Blocking I/O):同步阻塞 I/O
  2. NIO (New I/O):同步非阻塞 I/O
  3. AIO (Asynchronous I/O):异步非阻塞 I/O

二、BIO (Blocking I/O)

1. 基本概念

BIO 是传统的同步阻塞 I/O 模型,每个连接都需要一个独立的线程处理。

2. 使用场景

  • 连接数较少且固定的应用
  • 简单的客户端/服务器应用
  • 需要简单编程模型的应用

3. 核心类

  • InputStream/OutputStream:字节流基类
  • Reader/Writer:字符流基类
  • FileInputStream/FileOutputStream:文件字节流
  • FileReader/FileWriter:文件字符流
  • BufferedReader/BufferedWriter:缓冲字符流
  • Socket/ServerSocket:网络通信

4. 代码示例

文件读写示例

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
import java.io.*;

public class BIOFileExample {
public static void main(String[] args) {
// 写入文件
try (FileWriter writer = new FileWriter("test.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
bufferedWriter.write("Hello, BIO!");
bufferedWriter.newLine();
bufferedWriter.write("这是BIO文件写入示例");
} catch (IOException e) {
e.printStackTrace();
}

// 读取文件
try (FileReader reader = new FileReader("test.txt");
BufferedReader bufferedReader = new BufferedReader(reader)) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}

// 二进制文件读写
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
} 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
import java.io.*;
import java.net.*;

// BIO 服务器
public class BIOServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("BIO服务器启动,端口:8080");

while (true) {
Socket clientSocket = serverSocket.accept(); // 阻塞等待客户端连接
new Thread(() -> handleClient(clientSocket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}

private static void handleClient(Socket clientSocket) {
try (InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
PrintWriter writer = new PrintWriter(output, true)) {

String request;
while ((request = reader.readLine()) != null) {
System.out.println("收到客户端消息: " + request);
writer.println("服务器响应: " + request);

if ("exit".equalsIgnoreCase(request)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

// BIO 客户端
public class BIOClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {

System.out.println("已连接到服务器,输入消息开始聊天(输入exit退出):");

String userInput;
while ((userInput = consoleReader.readLine()) != null) {
writer.println(userInput);
String response = reader.readLine();
System.out.println("服务器响应: " + response);

if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

三、NIO (New I/O)

1. 基本概念

NIO 提供了非阻塞、多路复用的 I/O 操作方式,核心组件包括:

  • Channel:数据传输通道
  • Buffer:数据缓冲区
  • Selector:多路复用器

2. 使用场景

  • 高并发网络应用
  • 需要处理大量连接的应用
  • 需要非阻塞 I/O 的应用

3. 核心类

  • Channel:通道接口
  • SocketChannel/ServerSocketChannel:网络通道
  • FileChannel:文件通道
  • ByteBuffer:字节缓冲区
  • Selector:多路复用器
  • Charset:字符编码解码

4. 代码示例

文件读写示例

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
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;

public class NIOFileExample {
public static void main(String[] args) {
// 写入文件
Path path = Paths.get("test_nio.txt");
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

String content = "Hello, NIO!\n这是NIO文件写入示例";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(content.getBytes(StandardCharsets.UTF_8));
buffer.flip(); // 切换为读模式
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}

// 读取文件
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
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 content = new String(data, StandardCharsets.UTF_8);
System.out.println("文件内容: " + content);
}
} catch (IOException e) {
e.printStackTrace();
}

// 文件复制(零拷贝)
try (FileChannel sourceChannel = FileChannel.open(Paths.get("source.jpg"), StandardOpenOption.READ);
FileChannel destChannel = FileChannel.open(Paths.get("copy_nio.jpg"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
} catch (IOException e) {
e.printStackTrace();
}

// 内存映射文件
try (FileChannel channel = FileChannel.open(Paths.get("large_file.dat"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {

// 将文件的前1024字节映射到内存
MappedByteBuffer mappedBuffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, 1024);

// 直接操作内存中的数据
mappedBuffer.put(0, (byte) 'H');
mappedBuffer.put(1, (byte) 'i');
} 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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

// NIO 服务器
public class NIOServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));

Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("NIO服务器启动,端口:8080");

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

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

if (key.isAcceptable()) {
handleAccept(key, selector);
} else if (key.isReadable()) {
handleRead(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接: " + clientChannel.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);

// 回复客户端
String response = "服务器响应: " + message;
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
channel.write(responseBuffer);
} else if (bytesRead == -1) {
System.out.println("客户端断开连接: " + channel.getRemoteAddress());
channel.close();
}
}
}

// NIO 客户端
public class NIOClient {
public static void main(String[] args) {
try {
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("localhost", 8080));

// 等待连接完成
while (!channel.finishConnect()) {
Thread.sleep(10);
}

System.out.println("已连接到服务器,输入消息开始聊天(输入exit退出):");

// 启动读取线程
new Thread(() -> {
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.isConnected()) {
int bytesRead = channel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String response = new String(data);
System.out.println("服务器响应: " + response);
buffer.clear();
}
Thread.sleep(100);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();

// 发送消息
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = consoleReader.readLine()) != null) {
ByteBuffer buffer = ByteBuffer.wrap((userInput + "\n").getBytes());
channel.write(buffer);

if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}

channel.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

四、AIO (Asynchronous I/O)

1. 基本概念

AIO 是真正的异步非阻塞 I/O,基于回调机制实现。

2. 使用场景

  • 需要处理大量长连接的应用
  • 需要真正异步 I/O 的应用
  • 高性能服务器应用

3. 核心类

  • AsynchronousSocketChannel/AsynchronousServerSocketChannel:异步网络通道
  • AsynchronousFileChannel:异步文件通道
  • CompletionHandler:完成处理器

4. 代码示例

文件读写示例

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
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AIOFileExample {
public static void main(String[] args) {
Path path = Paths.get("test_aio.txt");

// 写入文件
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

String content = "Hello, AIO!\n这是AIO文件写入示例";
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());

Future<Integer> operation = channel.write(buffer, 0);
while (!operation.isDone()) {
// 等待写入完成
Thread.sleep(10);
}

System.out.println("写入完成,字节数: " + operation.get());
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}

// 读取文件
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ)) {

ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> operation = channel.read(buffer, 0);

while (!operation.isDone()) {
// 等待读取完成
Thread.sleep(10);
}

int bytesRead = operation.get();
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String content = new String(data);
System.out.println("文件内容: " + content);
}
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}

// 使用CompletionHandler异步读取文件
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ)) {

ByteBuffer buffer = ByteBuffer.allocate(1024);

channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] data = new byte[attachment.remaining()];
attachment.get(data);
String content = new String(data);
System.out.println("异步读取完成: " + content);
}

@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("读取失败: " + exc.getMessage());
}
});

// 主线程继续执行其他任务
System.out.println("主线程继续执行...");
Thread.sleep(1000); // 等待异步操作完成
} catch (IOException | InterruptedException 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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

// AIO 服务器
public class AIOServer {
public static void main(String[] args) {
try {
AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));

System.out.println("AIO服务器启动,端口:8080");

// 异步接受连接
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
// 继续接受其他连接
serverChannel.accept(null, this);

// 处理客户端连接
handleClient(clientChannel);
}

@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});

// 防止主线程退出
Thread.sleep(Long.MAX_VALUE);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

private static void handleClient(AsynchronousSocketChannel clientChannel) {
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 异步读取数据
clientChannel.read(buffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer bytesRead, Void attachment) {
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data);
System.out.println("收到客户端消息: " + message);

// 回复客户端
String response = "服务器响应: " + message;
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());

clientChannel.write(responseBuffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer bytesWritten, Void attachment) {
if (bytesWritten > 0) {
System.out.println("响应发送成功");
}

// 继续读取下一次消息
buffer.clear();
clientChannel.read(buffer, null, this);
}

@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
} else {
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}

// AIO 客户端
public class AIOClient {
public static void main(String[] args) {
try {
AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();
Future<Void> connectFuture = clientChannel.connect(new InetSocketAddress("localhost", 8080));
connectFuture.get(); // 等待连接完成

System.out.println("已连接到服务器,输入消息开始聊天(输入exit退出):");

// 启动读取线程
new Thread(() -> {
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (clientChannel.isOpen()) {
Future<Integer> readFuture = clientChannel.read(buffer);
int bytesRead = readFuture.get();
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String response = new String(data);
System.out.println("服务器响应: " + response);
buffer.clear();
}
Thread.sleep(100);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}).start();

// 发送消息
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = consoleReader.readLine()) != null) {
ByteBuffer buffer = ByteBuffer.wrap(userInput.getBytes());
Future<Integer> writeFuture = clientChannel.write(buffer);
writeFuture.get(); // 等待写入完成

if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}

clientChannel.close();
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

五、三种 I/O 模型的对比

特性 BIO NIO AIO
通信方式 同步阻塞 同步非阻塞 异步非阻塞
编程难度 简单 复杂 复杂
可靠性
吞吐量
线程模型 一个连接一个线程 一个线程处理多个连接 一个线程处理多个连接
适用场景 连接数少且固定 连接数多且短连接 连接数多且长连接

六、文件操作总结

1. 文件操作方式对比

操作方式 特点 适用场景
BIO文件操作 简单易用,同步阻塞 小文件读写,简单应用
NIO文件操作 非阻塞,高性能 大文件读写,高并发场景
内存映射文件 零拷贝,直接操作内存 超大文件随机访问
AIO文件操作 异步非阻塞 高性能文件操作

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
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
import java.io.IOException;
import java.nio.file.*;
import java.util.List;

public class FileOperationsBestPractice {
public static void main(String[] args) {
// 1. 使用Files类简化文件操作
Path path = Paths.get("example.txt");

try {
// 写入文件
Files.write(path, "Hello, Files API!".getBytes(), StandardOpenOption.CREATE);

// 读取文件
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
System.out.println(line);
}

// 文件属性
System.out.println("文件大小: " + Files.size(path) + " bytes");
System.out.println("最后修改时间: " + Files.getLastModifiedTime(path));

// 文件复制
Path copyPath = Paths.get("example_copy.txt");
Files.copy(path, copyPath, StandardCopyOption.REPLACE_EXISTING);

// 文件移动/重命名
Path newPath = Paths.get("example_renamed.txt");
Files.move(path, newPath, StandardCopyOption.REPLACE_EXISTING);

// 文件删除
Files.deleteIfExists(copyPath);

} catch (IOException e) {
e.printStackTrace();
}

// 2. 遍历目录
Path dir = Paths.get(".");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path file : stream) {
System.out.println(file.getFileName());
}
} catch (IOException e) {
e.printStackTrace();
}

// 3. 使用WatchService监控文件变化
try {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path dirPath = Paths.get(".");
dirPath.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);

new Thread(() -> {
while (true) {
WatchKey key;
try {
key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path changedPath = (Path) event.context();
System.out.println("文件变化: " + kind.name() + " - " + changedPath);
}
key.reset();
} catch (InterruptedException e) {
break;
}
}
}).start();

// 主线程继续执行
Thread.sleep(5000); // 监控5秒

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

七、实际应用建议

  1. BIO:适合连接数较少且固定的架构,编程简单
  2. NIO:适合连接数多且连接时间短的架构,如聊天服务器
  3. AIO:适合连接数多且连接时间长的架构,如相册服务器
  4. 文件操作
    • 小文件:使用BIO或Files API
    • 大文件:使用NIO或内存映射文件
    • 高性能需求:使用AIO

八、性能优化技巧

  1. 使用缓冲区合理设置大小
  2. 使用直接内存(DirectBuffer)减少内存拷贝
  3. 使用内存映射文件(MappedByteBuffer)提高文件读写性能
  4. 合理使用线程池管理连接
  5. 使用零拷贝技术减少数据复制
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
// 使用线程池的BIO服务器示例
import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class ThreadPoolBIOServer {
private static final int THREAD_POOL_SIZE = 10;

public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

try (ServerSocket serverSocket = new ServerSocket(8080)) {
System.out.println("线程池BIO服务器启动,端口:8080");

while (true) {
Socket clientSocket = serverSocket.accept();
executor.submit(() -> handleClient(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}

private static void handleClient(Socket clientSocket) {
try (InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
PrintWriter writer = new PrintWriter(output, true)) {

String request;
while ((request = reader.readLine()) != null) {
System.out.println("收到客户端消息: " + request);
writer.println("服务器响应: " + request);

if ("exit".equalsIgnoreCase(request)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

九、总结

Java I/O 体系提供了多种 I/O 模型以适应不同的应用场景:

  • BIO 简单易用,适合连接数少的场景
  • NIO 提供了高性能的非阻塞 I/O,适合高并发场景
  • AIO 提供了真正的异步 I/O,适合长连接场景
  • 文件操作 有多种方式,可根据文件大小和性能需求选择

二、面试题梳理

一、BIO (Blocking I/O) 相关面试题

1. 什么是BIO?它的工作原理是什么?

答案
BIO是同步阻塞I/O模型,每个连接都需要一个独立的线程处理。当线程调用read()或write()时,该线程会被阻塞,直到数据读取或写入完成。

核心特点

  • 一个连接一个线程
  • 读写操作会阻塞线程
  • 编程模型简单

2. BIO的优缺点是什么?

优点

  • 编程简单,易于理解
  • 适合连接数少的场景

缺点

  • 线程开销大,每个连接都需要独立线程
  • 并发性能差,不适合高并发场景
  • 线程阻塞导致资源浪费

3. 如何在BIO中处理多个客户端连接?

答案
使用线程池来管理连接线程,避免为每个连接创建新线程。

1
2
3
4
5
6
7
8
// 线程池BIO服务器示例
ExecutorService threadPool = Executors.newFixedThreadPool(10);
try (ServerSocket serverSocket = new ServerSocket(8080)) {
while (true) {
Socket clientSocket = serverSocket.accept();
threadPool.execute(() -> handleClient(clientSocket));
}
}

4. BIO适合什么场景?

使用场景

  • 连接数较少且固定的应用
  • 简单的客户端/服务器应用
  • 需要简单编程模型的应用

二、NIO (New I/O) 相关面试题

1. 什么是NIO?与BIO的主要区别是什么?

答案
NIO是同步非阻塞I/O模型,基于Channel、Buffer、Selector三大核心组件。

主要区别

特性 BIO NIO
阻塞方式 阻塞I/O 非阻塞I/O
线程模型 一个连接一个线程 一个线程处理多个连接
编程复杂度 简单 复杂
性能 连接数多时性能差 适合高并发场景

2. NIO的核心组件有哪些?各自的作用是什么?

答案

  • Channel:数据传输通道,支持异步读写
  • Buffer:数据缓冲区,用于与Channel交互
  • Selector:多路复用器,监控多个Channel的状态

3. 什么是Selector?它是如何工作的?

答案
Selector是多路复用器,可以监控多个Channel的IO事件(读、写、连接等)。

工作原理

  1. 将Channel注册到Selector
  2. 调用select()方法阻塞等待事件发生
  3. 获取发生事件的SelectionKey集合
  4. 处理相应的事件

4. Buffer的工作机制是什么?

答案
Buffer有三个重要属性:

  • position:当前读写位置
  • limit:读写限制
  • capacity:缓冲区容量

工作流程

1
2
3
4
5
ByteBuffer buffer = ByteBuffer.allocate(1024); // position=0, limit=1024, capacity=1024
buffer.put(data); // position=数据长度, limit=1024, capacity=1024
buffer.flip(); // position=0, limit=数据长度, capacity=1024
buffer.get(); // position递增
buffer.clear(); // position=0, limit=1024, capacity=1024

5. NIO适合什么场景?

使用场景

  • 高并发网络应用
  • 需要处理大量连接的应用
  • 需要非阻塞I/O的应用

三、AIO (Asynchronous I/O) 相关面试题

1. 什么是AIO?与NIO的区别是什么?

答案
AIO是异步非阻塞I/O模型,基于回调机制实现真正的异步IO。

主要区别

特性 NIO AIO
IO模型 同步非阻塞 异步非阻塞
编程方式 轮询方式 回调方式
复杂度 较高 很高
性能 更高

2. AIO的核心组件有哪些?

答案

  • AsynchronousSocketChannel/AsynchronousServerSocketChannel:异步网络通道
  • AsynchronousFileChannel:异步文件通道
  • CompletionHandler:完成处理器接口

3. AIO的两种编程方式是什么?

答案

  1. Future方式:返回Future对象,通过get()方法等待结果
  2. Callback方式:实现CompletionHandler接口,通过回调处理结果

4. AIO适合什么场景?

使用场景

  • 需要处理大量长连接的应用
  • 需要真正异步I/O的应用
  • 高性能服务器应用

四、文件IO操作相关面试题

1. Java中有哪些文件读写方式?

答案

  1. BIO方式:FileInputStream/FileOutputStream, FileReader/FileWriter
  2. NIO方式:FileChannel, Files API
  3. AIO方式:AsynchronousFileChannel

2. NIO.2的Files类提供了哪些优势?

答案

  • 简化文件操作:readAllLines(), write(), copy(), move()等方法
  • 更好的异常处理
  • 支持文件属性操作
  • 提供WatchService监控文件变化

3. 什么是内存映射文件?有什么优势?

答案
内存映射文件通过MappedByteBuffer将文件直接映射到内存中。

优势

  • 零拷贝,避免数据在用户空间和内核空间之间复制
  • 大文件处理效率高
  • 支持随机访问
1
2
3
4
5
6
// 内存映射文件示例
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 直接操作内存中的数据
}

4. 如何高效地进行大文件复制?

答案
使用NIO的transferTo()或transferFrom()方法,实现零拷贝。

1
2
3
4
5
// 零拷贝文件复制
try (FileChannel source = new FileInputStream("source.txt").getChannel();
FileChannel dest = new FileOutputStream("dest.txt").getChannel()) {
source.transferTo(0, source.size(), dest);
}

五、综合面试题

1. 三种IO模型的适用场景分别是什么?

答案

  • BIO:连接数少且固定的简单应用
  • NIO:高并发、短连接的服务器应用
  • AIO:高并发、长连接的高性能服务器应用

2. 什么是零拷贝?Java中如何实现?

答案
零拷贝技术避免数据在内存中的多次拷贝,提高IO性能。

实现方式

  1. NIO的transferTo()/transferFrom()方法
  2. 内存映射文件(MappedByteBuffer)
  3. FileChannel的直接传输

3. 如何选择适合的IO模型?

选择建议

  • 简单应用:BIO
  • 高并发网络应用:NIO
  • 高性能长连接应用:AIO
  • 文件操作:根据文件大小选择BIO或NIO

4. NIO的Selector为什么比BIO的性能好?

答案

  • 减少线程数:一个Selector线程可以处理多个连接,而BIO需要为每个连接创建一个线程
  • 避免线程阻塞:Selector通过事件驱动方式工作,只有在有IO事件时才进行处理
  • 减少上下文切换:线程数减少意味着更少的线程上下文切换开销

5. 如何处理NIO中的粘包和拆包问题?

答案
解决方案

  1. 固定长度:每个消息固定长度,不足补位
  2. 分隔符:使用特殊字符作为消息边界
  3. 长度字段:在消息头中添加长度字段
1
2
3
4
5
6
7
// 使用长度字段处理粘包拆包
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取消息头(长度字段)
int length = buffer.getInt();
// 根据长度读取消息体
byte[] body = new byte[length];
buffer.get(body);

六、性能优化相关面试题

1. 如何优化Java IO性能?

优化策略

  1. 使用合适的缓冲区大小
  2. 使用直接缓冲区(DirectBuffer)减少内存拷贝
  3. 使用零拷贝技术
  4. 合理使用线程池
  5. 批量处理IO操作

2. 直接缓冲区(DirectBuffer)和堆缓冲区(HeapBuffer)的区别?

答案

  • HeapBuffer:在JVM堆内存中分配,受垃圾回收影响
  • DirectBuffer:在堆外内存分配,不受垃圾回收直接影响,但创建和销毁成本更高

使用建议

  • 长期存在的大缓冲区:使用DirectBuffer
  • 短期使用的小缓冲区:使用HeapBuffer

3. 如何监控和诊断IO性能问题?

诊断工具

  1. jstack:查看线程状态,检查是否有IO阻塞
  2. jstat:监控GC情况,检查DirectBuffer的内存使用
  3. 操作系统工具:iostat, netstat等
  4. APM工具:Arthas, SkyWalking等

七、实际应用场景

1. 聊天服务器应该选择哪种IO模型?

答案
推荐使用NIO,因为:

  • 聊天服务器需要处理大量并发连接
  • 消息通常较短,适合NIO的事件驱动模型
  • 可以实现高并发和高吞吐量

2. 文件上传服务器应该选择哪种IO模型?

答案

  • 小文件上传:可以使用BIO,编程简单
  • 大文件上传:推荐使用NIO或AIO,支持零拷贝和异步处理
  • 高并发文件上传:必须使用NIO或AIO

3. 数据库连接池适合哪种IO模型?

答案
数据库连接通常使用BIO模型,因为:

  • 数据库连接数通常有限且固定
  • 每个查询需要完整的请求-响应过程
  • 连接池已经解决了线程开销问题

总结

Java IO相关的面试题主要围绕三种IO模型(BIO、NIO、AIO)和文件操作展开。重点需要掌握:

  1. 各种IO模型的特点和适用场景
  2. NIO的核心组件和工作原理
  3. 文件操作的最佳实践
  4. 性能优化策略
  5. 实际应用场景的选择

理解这些概念并能够结合实际场景进行分析,是应对Java IO相关面试题的关键。