一、 I/O流:基础数据处理模型
在Java中,数据如同水流,I/O流则是连接数据源与程序的管道。这套基于“流”的模型,是处理输入/输出操作的传统方式。
字节流是处理原始字节数据(如图片、视频等二进制文件)的基础抽象类。
常见实现类:
- FileInputStream / FileOutputStream: 用于文件字节数据的读写,是最基础的节点流。
- ByteArrayInputStream / ByteArrayOutputStream: 将内存中的字节数组封装为流,适用于内存数据操作。
- BufferedInputStream / BufferedOutputStream: 缓冲流,为核心流增加缓冲区。它能显著提升性能,例如从文件读取时,会一次性将较多数据加载到内存缓冲区,减少实际磁盘I/O次数。
- DataInputStream / DataOutputStream: 提供直接读写Java基本数据类型(如int, double)和字符串的方法。
- ObjectInputStream / ObjectOutputStream: 用于对象的序列化与反序列化,可直接读写实现了
Serializable接口的Java对象。
优缺点分析:
- 优点:模型直观,适用于所有二进制数据。
- 缺点:直接操作字节,处理文本时需手动处理字符编码,较为繁琐。
代码示例:
// 正例:使用缓冲流高效复制文件
try (FileInputStream fis = new FileInputStream("source.jpg");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("copy.jpg");
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
// 反例:无缓冲单字节读写,性能极差
try (FileInputStream fis = new FileInputStream("bigFile.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int b;
while ((b = fis.read()) != -1) { // 每次系统调用仅读取一个字节
fos.write(b);
}
}
// **性能分析**:无缓冲时,每次`read()`/`write()`都涉及用户态与内核态的上下文切换,对于大文件操作,耗时可能比缓冲流高数个数量级。
2. 装饰器模式的应用:Filter流
Java I/O流通过装饰器模式动态扩展功能。FilterInputStream和FilterOutputStream是装饰器类的基类,像BufferedInputStream、DataInputStream都是其子类。例如,PushbackInputStream允许将已读取的字节“推回”流中,供下次读取。
优点:灵活组合,符合开闭原则。
缺点:多层嵌套可能导致代码可读性下降。
示例:
// 组合装饰:一个具备缓冲、回推、读取基本类型功能的流
try (DataInputStream dis = new DataInputStream(
new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("data.bin"), 1024), 8))) {
int anInt = dis.readInt(); // 高效、灵活地读取数据
}
3. 字符流:Reader与Writer
为简化文本处理而设计,字符流在内部自动处理字符编码转换。
常见实现类:
- InputStreamReader / OutputStreamWriter: 作为字节流与字符流之间的桥梁,构造时必须指定字符集(Charset),如
StandardCharsets.UTF_8。
- FileReader / FileWriter: 读写文本文件的便捷类。但默认使用平台编码,易导致乱码,生产环境不建议使用。
- BufferedReader / BufferedWriter: 提供缓冲功能,
BufferedReader的readLine()方法可方便地按行读取。
- PrintWriter: 提供格式化的打印方法(如
print, printf),System.out即是其子类PrintStream。
代码示例:
// 正例:明确指定UTF-8编码读取文本文件
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 反例:依赖默认编码的FileReader,存在乱码风险
try (FileReader fr = new FileReader("text.txt")) {
// 若文件编码与系统默认编码不符,则产生乱码
}
4. 随机访问文件:RandomAccessFile
此类允许对文件内容进行随机读写,通过seek(long pos)方法移动文件指针。
特点:同时实现DataInput和DataOutput接口。
适用场景:修改文件局部内容、实现简单数据库索引等。但其API较为古老,对于高性能场景,现代Java应用通常倾向于使用NIO的FileChannel。
示例:
// 在文件末尾追加日志
try (RandomAccessFile raf = new RandomAccessFile("log.txt", "rw")) {
raf.seek(raf.length()); // 定位至文件末尾
raf.writeBytes("\nNew log entry at " + new Date());
}
5. I/O流的最佳实践总结
- 装饰器嵌套:从节点流开始,按需包装功能流。
- 使用Try-With-Resources:确保流资源自动关闭,避免泄漏。
- 强制使用缓冲:处理文件或网络I/O时,务必使用缓冲流提升性能。
- 文本编码显式指定:始终使用
InputStreamReader/OutputStreamWriter并明确传递Charset。
// 标准模板:读取UTF-8文本文件并打印行号
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("input.txt"), StandardCharsets.UTF_8))) {
String line;
int lineNum = 1;
while ((line = br.readLine()) != null) {
System.out.println(lineNum++ + ": " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
二、 标准I/O与进程控制
Java程序启动时即拥有三个标准流:System.in(标准输入)、System.out(标准输出)、System.err(标准错误)。
1. 标准输入输出重定向
可以改变标准流的默认绑定,例如将输出重定向至文件。
// 将标准输出重定向到日志文件
PrintStream originalOut = System.out;
try (PrintStream fileOut = new PrintStream(new FileOutputStream("output.log"))) {
System.setOut(fileOut);
System.out.println("此内容将写入文件,而非控制台。");
} finally {
System.setOut(originalOut); // 恢复
}
System.out.println("输出已恢复至控制台。");
应用:日志捕获、自动化测试。
2. 执行外部进程
通过ProcessBuilder或Runtime.exec()可调用系统命令或外部程序,并与其输入输出流交互。
// 执行命令并读取结果
Process process = new ProcessBuilder("ls", "-la").start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
reader.lines().forEach(System.out::println);
}
int exitCode = process.waitFor();
System.out.println("进程退出码: " + exitCode);
三、 新I/O(NIO):高性能I/O模型
NIO引入了通道(Channel) 和缓冲区(Buffer) 概念,支持非阻塞I/O,性能更高,尤其适合高并发场景。
1. 核心:ByteBuffer
ByteBuffer是一个可读写原生数据的容器,通过position, limit, capacity三个状态变量控制读写。
标准操作流程:
- 写入数据 (
put) -> position增加。
- 切换为读模式 (
flip) -> limit设置为当前position,position归零。
- 读取数据 (
get) -> position增加。
- 清除缓冲区 (
clear 或 compact) -> 准备下一轮写入。
ByteBuffer buf = ByteBuffer.allocate(48);
// 写入
buf.put((byte) 1);
buf.putInt(42);
// 切换读模式
buf.flip();
// 读取(必须按写入顺序和类型)
byte aByte = buf.get();
int anInt = buf.getInt();
2. 字符编码解码
Charset类用于在字节缓冲区与字符缓冲区间转换。
// 字符串编码为ByteBuffer
ByteBuffer buffer = StandardCharsets.UTF_8.encode("Hello NIO");
// ByteBuffer解码为字符串
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
String str = charBuffer.toString();
3. 视图缓冲区
通过asXxxBuffer()方法(如asIntBuffer())创建与原始ByteBuffer共享底层数据的视图,以特定数据类型视角操作数据,简化计算。
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.putInt(100);
byteBuffer.putDouble(3.14);
byteBuffer.flip();
IntBuffer intBuffer = byteBuffer.asIntBuffer();
int firstInt = intBuffer.get(); // 读取100
// 原byteBuffer的position已前进
DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
double aDouble = doubleBuffer.get(); // 读取3.14
4. 内存映射文件(MappedByteBuffer)
NIO的杀手锏,允许将文件的一部分或全部直接映射到进程内存中,操作系统负责页缓存,使得文件读写速度接近内存操作。
try (RandomAccessFile file = new RandomAccessFile("huge.data", "rw");
FileChannel channel = file.getChannel()) {
// 映射模式,起始位置,映射大小
MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 100);
// 像操作数组一样直接修改文件内容
for (int i = 0; i < mappedBuffer.limit(); i++) {
byte b = mappedBuffer.get(i);
mappedBuffer.put(i, (byte)(b + 1));
}
// mappedBuffer.force(); // 可强制将更改刷写到磁盘
}
适用场景:处理超大文件,性能远超传统流式I/O,在数据库/中间件等领域有广泛应用。
5. 文件锁
NIO提供了更灵活的文件锁机制,支持排他锁和共享锁,并可锁定文件特定区域。
try (FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.WRITE)) {
FileLock lock = channel.lock(); // 获取排他锁,阻塞直到获取成功
try {
channel.write(ByteBuffer.wrap("Locked data".getBytes()));
} finally {
lock.release();
}
}
注意:Java文件锁通常是“劝告式(Advisory)”的,仅对同样检查锁的进程有效。
总结与选型建议
Java提供了两套完整的I/O方案:
- 传统I/O流 (java.io):基于流的同步阻塞模型,API简单直观,适用于大多数常规文件操作和并发要求不高的场景。
- 新I/O (java.nio):基于通道和缓冲区的模型,支持非阻塞和内存映射等高级特性,适用于高并发网络应用、云原生微服务架构或需要处理超大文件的场景。
选型指南:
- 文本处理:优先使用
Reader/Writer,并始终显式指定字符编码。
- 文件复制与读写:务必使用缓冲流(
BufferedInputStream/BufferedOutputStream或BufferedReader/BufferedWriter)包装基础流。
- 高性能与随机访问:考虑使用NIO的
FileChannel与ByteBuffer,尤其是内存映射文件(MappedByteBuffer)对于大文件操作有颠覆性性能提升。
- 网络编程与高并发:直接选择NIO的非阻塞通道(
Selector、SocketChannel等)。
理解两套API的原理与适用场景,方能根据实际需求选择最合适的工具,构建高效稳健的应用程序。