一、知识点梳理
一、Java I/O 概述
Java I/O 主要分为以下几种模型:
- BIO (Blocking I/O):同步阻塞 I/O
- NIO (New I/O):同步非阻塞 I/O
- 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.*;
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(); } } } }
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)) { 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;
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(); } } }
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(); } 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;
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(); } } }); } }
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) { 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(); } 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(); } 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); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
|
七、实际应用建议
- BIO:适合连接数较少且固定的架构,编程简单
- NIO:适合连接数多且连接时间短的架构,如聊天服务器
- AIO:适合连接数多且连接时间长的架构,如相册服务器
- 文件操作:
- 小文件:使用BIO或Files API
- 大文件:使用NIO或内存映射文件
- 高性能需求:使用AIO
八、性能优化技巧
- 使用缓冲区合理设置大小
- 使用直接内存(DirectBuffer)减少内存拷贝
- 使用内存映射文件(MappedByteBuffer)提高文件读写性能
- 合理使用线程池管理连接
- 使用零拷贝技术减少数据复制
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
| 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
| 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事件(读、写、连接等)。
工作原理:
- 将Channel注册到Selector
- 调用select()方法阻塞等待事件发生
- 获取发生事件的SelectionKey集合
- 处理相应的事件
4. Buffer的工作机制是什么?
答案:
Buffer有三个重要属性:
- position:当前读写位置
- limit:读写限制
- capacity:缓冲区容量
工作流程:
1 2 3 4 5
| ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(data); buffer.flip(); buffer.get(); buffer.clear();
|
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的两种编程方式是什么?
答案:
- Future方式:返回Future对象,通过get()方法等待结果
- Callback方式:实现CompletionHandler接口,通过回调处理结果
4. AIO适合什么场景?
使用场景:
- 需要处理大量长连接的应用
- 需要真正异步I/O的应用
- 高性能服务器应用
四、文件IO操作相关面试题
1. Java中有哪些文件读写方式?
答案:
- BIO方式:FileInputStream/FileOutputStream, FileReader/FileWriter
- NIO方式:FileChannel, Files API
- 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性能。
实现方式:
- NIO的transferTo()/transferFrom()方法
- 内存映射文件(MappedByteBuffer)
- FileChannel的直接传输
3. 如何选择适合的IO模型?
选择建议:
- 简单应用:BIO
- 高并发网络应用:NIO
- 高性能长连接应用:AIO
- 文件操作:根据文件大小选择BIO或NIO
4. NIO的Selector为什么比BIO的性能好?
答案:
- 减少线程数:一个Selector线程可以处理多个连接,而BIO需要为每个连接创建一个线程
- 避免线程阻塞:Selector通过事件驱动方式工作,只有在有IO事件时才进行处理
- 减少上下文切换:线程数减少意味着更少的线程上下文切换开销
5. 如何处理NIO中的粘包和拆包问题?
答案:
解决方案:
- 固定长度:每个消息固定长度,不足补位
- 分隔符:使用特殊字符作为消息边界
- 长度字段:在消息头中添加长度字段
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性能?
优化策略:
- 使用合适的缓冲区大小
- 使用直接缓冲区(DirectBuffer)减少内存拷贝
- 使用零拷贝技术
- 合理使用线程池
- 批量处理IO操作
2. 直接缓冲区(DirectBuffer)和堆缓冲区(HeapBuffer)的区别?
答案:
- HeapBuffer:在JVM堆内存中分配,受垃圾回收影响
- DirectBuffer:在堆外内存分配,不受垃圾回收直接影响,但创建和销毁成本更高
使用建议:
- 长期存在的大缓冲区:使用DirectBuffer
- 短期使用的小缓冲区:使用HeapBuffer
3. 如何监控和诊断IO性能问题?
诊断工具:
- jstack:查看线程状态,检查是否有IO阻塞
- jstat:监控GC情况,检查DirectBuffer的内存使用
- 操作系统工具:iostat, netstat等
- APM工具:Arthas, SkyWalking等
七、实际应用场景
1. 聊天服务器应该选择哪种IO模型?
答案:
推荐使用NIO,因为:
- 聊天服务器需要处理大量并发连接
- 消息通常较短,适合NIO的事件驱动模型
- 可以实现高并发和高吞吐量
2. 文件上传服务器应该选择哪种IO模型?
答案:
- 小文件上传:可以使用BIO,编程简单
- 大文件上传:推荐使用NIO或AIO,支持零拷贝和异步处理
- 高并发文件上传:必须使用NIO或AIO
3. 数据库连接池适合哪种IO模型?
答案:
数据库连接通常使用BIO模型,因为:
- 数据库连接数通常有限且固定
- 每个查询需要完整的请求-响应过程
- 连接池已经解决了线程开销问题
总结
Java IO相关的面试题主要围绕三种IO模型(BIO、NIO、AIO)和文件操作展开。重点需要掌握:
- 各种IO模型的特点和适用场景
- NIO的核心组件和工作原理
- 文件操作的最佳实践
- 性能优化策略
- 实际应用场景的选择
理解这些概念并能够结合实际场景进行分析,是应对Java IO相关面试题的关键。