目录
1.5. ObjectInputStream / ObjectOutputStream
一、引言
java 提供了丰富的 I/O 支持,用于处理文件读写、网络通信等场景。理解 Java IO 和 NIO 的基本概念、流的分类以及不同 I/O 模型的工作机制,对于构建高性能、高并发的应用至关重要。
本文将从 Java IO 的基础流分类开始,介绍字节流和字符流的基本类及其使用方法;接着深入讲解常见的五种 I/O 模型在 Java 中的实现方式和适用场景,并提供简单易懂的代码示例,帮助你建立完整的知识体系。
以下是一个展示Java中io流的思维导图:
二、Java IO 流分类详解
List接口继承自Collection接口,提供了额外的功能来处理索引位置上的元素。与Set、Map不同,List允许包含重复的元素,并且可以通过索引来访问或修改特定位置的元素。
1. 字节流(Byte Stream)
用于处理二进制数据,以字节为单位进行读写操作。所有字节流都继承自 InputStream 和 OutputStream。
常见子类:
FileInputStream / FileOutputStream:用于文件的字节读写。
BufferedInputStream / BufferedOutputStream:带缓冲区的字节流,提高效率。
ObjectInputStream / ObjectOutputStream:支持对象序列化与反序列化。
1.1.FileInputStream
// 用于从文件中读取字节数据
try (FileInputStream fis = new FileInputStream("input.txt")) {
int data;
while ((data = fis.read()) != -1) { // 每次读取一个字节
System.out.print((char) data); // 转换为字符输出
}
} catch (IOException e) {
e.printStackTrace();
}
1.2.FileOutputStream
// 用于向文件中写入字节数据
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "Hello, FileOutputStream!";
fos.write(content.getBytes()); // 写入字符串的字节形式
} catch (IOException e) {
e.printStackTrace();
}
1.3.BufferedInputStream
// 带缓冲区的输入流,提高读取效率
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"))) {
int data;
while ((data = bis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
实现方式:一次性从底层输入流读取一块数据到内存缓冲区中,后续的 read() 调用直接从缓冲区中获取数据,直到缓冲区耗尽再重新加载。
1.4.BufferedOutputStream
// 带缓冲区的输出流,提高写入效率
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("buffered_output.txt"))) {
String content = "This is a buffered output example.";
bos.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
1.5. ObjectInputStream / ObjectOutputStream
// 序列化与反序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
Person person = new Person("Alice", 25);
oos.writeObject(person); // 写入对象
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person loadedPerson = (Person) ois.readObject(); // 读取对象
System.out.println(loadedPerson);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
2.字符流(Character Stream)
2.1.FileReader
// 用于读取文本文件中的字符
try (FileReader reader = new FileReader("input.txt")) {
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
2.2.FileWriter
// 用于向文本文件写入字符内容
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, FileWriter!"); // 写入字符串
} catch (IOException e) {
e.printStackTrace();
}
2.3.BufferedReader
// 高效读取文本行,支持按行读取
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = br.readLine()) != null) { // 按行读取
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
2.4.BufferedWriter
// 高效写入文本内容,支持换行
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("First line");
bw.newLine(); // 换行
bw.write("Second line");
} catch (IOException e) {
e.printStackTrace();
}
2.5.InputStreamReader
// 将字节流转换为字符流,可指定编码格式
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")) {
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
2.6.OutputStreamWriter
// 将字符流转换为字节流,可指定编码格式
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")) {
osw.write("Hello, OutputStreamWriter with UTF-8 encoding.");
} catch (IOException e) {
e.printStackTrace();
}
三、常见的五种 I/O 模型及实现
1.阻塞 I/O(Blocking I/O)
线程发起 I/O 请求后会一直等待,直到数据准备好并完成复制。
适用场景:
适用于连接数少、请求简单的应用,如小型局域网服务。
示例代码:
使用 ServerSocket 和 Socket 编写的 BIO 服务器
// 创建服务器端Socket,绑定到8080端口
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("阻塞IO服务器启动,等待客户端连接...");
while (true) {
// accept() 是一个阻塞方法,直到有客户端连接才会继续执行
Socket socket = serverSocket.accept();
System.out.println("客户端已连接");
// 处理客户端请求(单线程处理)
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = reader.readLine(); // readLine() 也是阻塞方法
System.out.println("收到消息:" + line);
// 向客户端发送响应
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("Hello from server\n");
writer.flush();
// 关闭资源
socket.close();
}
2.非阻塞 I/O(Non-blocking I/O)
I/O 操作不会阻塞线程,立即返回结果,适合需要控制线程行为的场景。
适用场景:
常用于底层网络库或需手动控制轮询的场景。
示例代码:
使用 NIO 的 SocketChannel 和非阻塞模式
// 打开客户端Socket通道
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞模式
socketChannel.configureBlocking(false);
// 尝试连接服务器
boolean connected = socketChannel.connect(new InetSocketAddress("localhost", 8080));
if (connected) {
System.out.println("已连接服务器");
} else {
System.out.println("未立即连接成功,可能正在建立连接...");
}
// 缓冲区用于读写数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 检查是否连接完成
while (!socketChannel.finishConnect()) {
System.out.println("等待连接完成...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 发送数据给服务端
String message = "Hello, Non-blocking IO!";
buffer.put(message.getBytes());
buffer.flip(); // 切换为读模式
socketChannel.write(buffer); // 写入数据
System.out.println("已发送消息");
// 清空缓冲区准备接收响应
buffer.clear();
// 尝试读取响应(非阻塞,如果没数据会返回0)
int bytesRead = socketChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip(); // 准备读取内容
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到响应:" + new String(data));
} else {
System.out.println("暂无响应数据");
}
// 关闭连接
socketChannel.close();
3.多路复用 I/O(I/O Multiplexing)
通过 Selector 监控多个通道的状态变化,实现单线程管理多个连接。
适用场景:
高并发服务器,如 Web 服务器、聊天服务等。
示例代码:
使用 Selector + SocketChannel/ServerSocketChannel
// 打开选择器
Selector selector = Selector.open();
// 打开服务器Socket通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
serverSocketChannel.configureBlocking(false); // 设置为非阻塞
// 注册到选择器,监听 OP_ACCEPT 事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("多路复用IO服务器启动,等待事件...");
while (true) {
// 阻塞等待事件发生(可设置超时时间)
int readyCount = selector.select();
if (readyCount == 0) continue;
// 获取所有就绪事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理连接事件
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept(); // 接受新连接
clientChannel.configureBlocking(false); // 设置为非阻塞
// 注册读事件
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接加入");
}
// 处理读事件
if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer); // 非阻塞读取
if (bytesRead > 0) {
buffer.flip(); // 切换为读模式
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到数据:" + new String(data));
// 回应客户端
ByteBuffer responseBuffer = ByteBuffer.wrap("Echo: ".getBytes());
ByteBuffer combined = ByteBuffer.allocate(responseBuffer.remaining() + buffer.remaining());
combined.put(responseBuffer);
combined.put(buffer);
combined.flip();
clientChannel.write(combined); // 发送响应
} else if (bytesRead == -1) {
// 客户端断开连接
System.out.println("客户端断开");
clientChannel.close();
}
}
// 移除当前事件,避免重复处理
iterator.remove();
}
}
4.信号驱动 I/O(Signal-driven I/O)
通过注册信号处理器,在数据就绪时由内核通知进程
Java 标准库不直接支持该模型,通常依赖操作系统级编程(如 Linux 的 SIGIO)。
5. 异步 I/O(Asynchronous I/O)
整个 I/O 操作(包括数据拷贝)都是异步完成的,线程无需等待。
使用场景:
高性能、大规模并发服务器,如数据库连接池、消息中间件。
示例代码:
使用 AsynchronousSocketChannel 和 CompletionHandler。
// 打开异步Socket通道
AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();
// 连接服务器
clientChannel.connect(new InetSocketAddress("localhost", 8080), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
System.out.println("连接建立成功");
// 发送数据
String message = "Hello, Asynchronous IO!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
clientChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if (buffer.hasRemaining()) {
// 继续发送剩余数据
clientChannel.write(buffer, buffer, this);
} else {
System.out.println("数据发送完成");
// 准备接收响应
ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
clientChannel.read(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer bytesRead, ByteBuffer attachment) {
attachment.flip(); // 切换为读模式
byte[] data = new byte[attachment.remaining()];
attachment.get(data);
System.out.println("收到响应:" + new String(data));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
// 保持主线程运行以等待异步操作完成
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
四、Java NIO详解
Java NIO(New Input/Output)是 Java 1.4 引入的一组新的 I/O API,相较于传统的 Java IO(即 BIO),它提供了更高效、更灵活的 I/O 操作方式,尤其适合高并发、高性能场景。
1.NIO的核心组件
1.1.Buffer(缓冲区)
数据读写的目的地和来源,所有数据必须通过 Buffer 进行操作。
1.1.1.Buffer 的基本结构
Buffer 是一个容器对象,内部维护了一个数组(如 byte[]),并记录了以下关键属性:
capacity:容量,表示 Buffer 最多能容纳的数据量。
position:当前读写位置。
limit:表示可操作数据的上限(从 position 到 limit 之间为有效数据)。
mark:标记位置,可以通过 reset() 回到该位置。
1.1.2.Buffer 的常用类型
1.1.3.Buffer 使用步骤
// 1. 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配1024字节的缓冲区
// 2. 写入数据
buffer.put("Hello NIO!".getBytes());
// 3. 切换为读模式
buffer.flip();
// 4. 读取数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
// 5. 清空缓冲区(或 compact)
buffer.clear(); // 或 buffer.compact();
flip():切换为读模式,将 position 设为0,limit 设为之前写入的位置。
clear():清空整个缓冲区,position=0,limit=capacity。
compact():保留未读完的数据,后续写入不会覆盖。
1.2.Channel(通道)
类似于流,但可以同时进行读写,并支持异步操作。
1.2.1.Channel 与 Stream 的区别
1.2.2.常见 Channel 实现类
代码示例:
try (FileInputStream fis = new FileInputStream("input.txt");
FileChannel channel = fis.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip(); // 切换为读模式
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // 清空准备下一次读取
bytesRead = channel.read(buffer);
}
}
1.3.Selector(选择器)
多路复用机制,用于监听多个 Channel 的事件(如连接、读就绪等)。
1.3.1.Selector 的基本流程
1.打开 Selector。
2.将 Channel 注册到 Selector 上,并指定感兴趣的事件(如 OP_READ、OP_WRITE 等)。
3.调用 select() 方法等待事件发生。
4.获取就绪事件集合,处理事件。
5.移除已处理的事件。
代码示例:同三.3多路复用IO。
1.4.NIO 的优势
非阻塞 I/O:避免线程阻塞在 I/O 操作上。
多路复用:一个线程可管理多个 Channel。
缓冲区机制:提高数据传输效率。
支持异步 I/O(AIO):适用于大规模并发。
1.5.适用场景
五、结语
通过对Java IO和NIO的学习,我们可以根据不同的应用场景选择合适的技术方案。对于简单的文件操作或者低并发需求的服务端来说,使用标准的IO就足够了;而对于需要处理大量并发连接的高性能服务端,则应该考虑采用NIO技术。希望这篇文章能帮助初学者和有一定经验的Java开发者更深入地理解这两种重要的I/O处理机制,并能够在实际项目中灵活运用它们。