Java IO
大致分为:
- 文件字节流:
FileInputStream
FileOutputStream
- 文件字符流:
FileReader
FileWriter
- 缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
- 转换流:
InputStreamReader
OutputStreamWriter
- 数据流:
DataInputStream
DataOutputStream
- 对象流(序列化):
ObjectInputStream
ObjectOutputStream
- 打印流:
PrintStream
PrintWriter
graph TD A[需要处理什么数据?] -->|二进制| B[需要缓冲?] A -->|文本| C[需要指定编码?] B -->|是| D[使用BufferedInputStream/OutputStream] B -->|否| E[直接使用FileInputStream/OutputStream] C -->|是| F[使用InputStreamReader/OutputStreamWriter] C -->|否| G[使用FileReader/FileWriter] D --> H[需要处理基本类型?] H -->|是| I[包装DataInputStream/OutputStream] H -->|否| J[需要对象序列化?] J -->|是| K[包装ObjectInputStream/OutputStream] J -->|否| L[需要压缩?] L -->|是| M[包装GZIPInputStream/OutputStream]
Java I/O 分类
一、字节流(处理二进制数据)
1. FileInputStream
常用 API:
int read()
: 读取单个字节(0-255),到达末尾返回-1int read(byte[] b)
: 读取数据填充字节数组,返回实际读取字节数int read(byte[] b, int off, int len)
: 读取指定长度的数据到数组的指定位置long skip(long n)
: 跳过 n 个字节int available()
: 返回可读取的字节估计数
深入分析:
// 示例:多种读取方式对比
try (FileInputStream fis = new FileInputStream("data.bin")) {
// 方式1:单字节读取(效率最低)
int singleByte;
while ((singleByte = fis.read()) != -1) {
// 处理每个字节...
}
// 方式2:批量读取(推荐)
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
processData(buffer, bytesRead); // 处理读取到的数据
}
// 方式3:精确控制读取
byte[] part = new byte[20];
fis.read(part, 5, 10); // 从part[5]开始填充,最多读10字节
}
最佳实践:
- 总是使用批量读取(
read(byte[])
)而非单字节读取 - 缓冲区大小建议设为 1024 的倍数(如 8192)
- 检查
bytesRead
值,它可能小于缓冲区长度
2. FileOutputStream
常用 API:
void write(int b)
: 写入单个字节(低 8 位)void write(byte[] b)
: 写入整个字节数组void write(byte[] b, int off, int len)
: 写入数组的指定部分void flush()
: 强制将缓冲数据写入物理存储FileChannel getChannel()
: 获取关联的 FileChannel(NIO)
深入分析:
// 示例:多种写入方式
try (FileOutputStream fos = new FileOutputStream("output.bin", true)) { // 追加模式
// 方式1:单字节写入
fos.write(65); // 写入'A'的ASCII码
// 方式2:批量写入
byte[] data = "Hello".getBytes(StandardCharsets.UTF_8);
fos.write(data);
// 方式3:部分写入
byte[] fullData = new byte[100];
// ...填充数据...
fos.write(fullData, 10, 20); // 只写入fullData[10]到[29]
// 重要:确保数据真正写入磁盘
fos.getFD().sync(); // 强制同步到磁盘
}
最佳实践:
- 优先使用批量写入方法
- 重要数据写入后调用
flush()
或getFD().sync()
- 考虑使用缓冲流包装提高性能
二、字符流(处理文本数据)
3. FileReader
常用 API:
int read()
: 读取单个字符int read(char[] cbuf)
: 读取字符到数组int read(char[] cbuf, int off, int len)
: 读取到数组指定位置long skip(long n)
: 跳过 n 个字符boolean ready()
: 判断是否可读
编码问题解决方案:
// 正确指定编码的FileReader替代方案
Reader reader = new InputStreamReader(
new FileInputStream("text.txt"),
StandardCharsets.UTF_8);
深入分析:
// 示例:处理大文本文件
try (Reader reader = new FileReader("large.txt")) {
char[] buffer = new char[8192];
int charsRead;
StringBuilder content = new StringBuilder();
while ((charsRead = reader.read(buffer)) != -1) {
content.append(buffer, 0, charsRead);
// 处理逻辑可以在这里添加
if (content.length() > 100_000) {
processChunk(content.toString());
content.setLength(0); // 清空缓冲区
}
}
// 处理剩余内容
if (content.length() > 0) {
processChunk(content.toString());
}
}
4. FileWriter
常用 API:
void write(int c)
: 写入单个字符void write(char[] cbuf)
: 写入字符数组void write(char[] cbuf, int off, int len)
: 写入数组的指定部分void write(String str)
: 写入字符串void write(String str, int off, int len)
: 写入字符串的指定部分void append(CharSequence csq)
: 追加字符序列
深入分析:
// 示例:日志文件写入
try (FileWriter fw = new FileWriter("app.log", true)) { // 追加模式
// 写入日志头
fw.write("===== 系统启动 =====\n");
// 批量写入日志项
String[] logs = getLogEntries();
for (String log : logs) {
fw.write(log);
fw.write("\n"); // 换行
// 每10条刷新一次
if (++count % 10 == 0) {
fw.flush();
}
}
// 写入日志尾
fw.append("\n===== 系统关闭 =====\n");
}
三、缓冲流(提高 I/O 效率)
5. BufferedInputStream(缓冲字节输入流)
用途:为字节输入流添加缓冲功能
// 示例:高效复制文件
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large.bin"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 处理数据...
}
}
分析:
- 内部维护一个缓冲区(默认 8KB)
- 减少实际磁盘读取次数
- 必须包装其他
InputStream
使用
6. BufferedOutputStream(缓冲字节输出流)
用途:缓冲字节输出
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.bin"))) {
for (int i = 0; i < 1000; i++) {
bos.write(i % 256); // 多次小量写入会被缓冲
}
} // 自动flush并关闭
分析:
- 小量多次写入时效率高
- 缓冲区满或流关闭时自动写入磁盘
- 可手动调用
flush()
强制写入
7. BufferedReader(缓冲字符输入流)
用途:高效读取文本
// 示例:逐行读取文本
try (BufferedReader br = new BufferedReader(
new FileReader("bigtext.txt"))) {
String line;
while ((line = br.readLine()) != null) { // 读取一行
System.out.println(line);
}
}
分析:
readLine()
是特有方法,方便文本处理- 默认缓冲区大小 8KB
- 通常包装 FileReader 使用
8. BufferedWriter(缓冲字符输出流)
用途:高效写入文本
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("log.txt"))) {
bw.write("第一行");
bw.newLine(); // 换行(跨平台安全)
bw.write("第二行");
} // 自动flush
分析:
newLine()
比直接写\n
更安全(兼容不同 OS)- 适合频繁写入小段文本的场景
四、转换流(字节↔字符转换)
9. InputStreamReader
用途:将字节流转换为字符流(可指定编码)
// 示例:用指定编码读取文本
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("data.txt"), "GBK")) {
char[] buf = new char[1024];
int len;
while ((len = isr.read(buf)) != -1) {
System.out.print(new String(buf, 0, len));
}
}
分析:
- 解决编码问题的关键类
- FileReader 实际上是
InputStreamReader
的子类
10. OutputStreamWriter
用途:将字符流转换为字节流
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("out.txt"), "UTF-8")) {
osw.write("使用UTF-8编码写入");
}
五、数据流(处理基本数据类型)
11. DataInputStream
核心 API:
readBoolean()
,readByte()
,readChar()
,readShort()
readInt()
,readLong()
,readFloat()
,readDouble()
readUTF()
: 读取 UTF-8 编码字符串readFully(byte[] b)
: 完全填充缓冲区
二进制文件解析:
// 示例:解析自定义二进制格式
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("data.dat")))) {
// 读取文件头
String magic = dis.readUTF(); // 读取标识符
int version = dis.readInt(); // 版本号
// 读取记录
while (dis.available() > 0) {
int id = dis.readInt();
double value = dis.readDouble();
boolean valid = dis.readBoolean();
processRecord(id, value, valid);
}
}
12. DataOutputStream
核心 API:
- 对应 DataInputStream 的所有 write 方法
writeUTF(String str)
: 写入 UTF-8 字符串size()
: 返回已写入字节数
二进制文件生成:
// 示例:生成自定义二进制文件
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("output.dat")))) {
// 写入文件头
dos.writeUTF("DATA"); // 魔数
dos.writeInt(2); // 版本
// 写入记录
for (DataRecord record : records) {
dos.writeInt(record.getId());
dos.writeDouble(record.getValue());
dos.writeBoolean(record.isValid());
}
System.out.println("文件大小: " + dos.size() + " 字节");
}
六、对象序列化流
13. ObjectInputStream
关键 API:
writeObject(Object obj)
/readObject()
writeUnshared(Object obj)
: 写入非共享对象reset()
: 重置对象引用表
自定义序列化:
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 不序列化
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 默认序列化
oos.writeObject(encrypt(password)); // 加密后序列化
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
this.password = decrypt((String)ois.readObject());
}
// 加密/解密方法省略...
}
// 使用示例
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("users.dat"))) {
User user = new User("admin", "secret");
oos.writeObject(user);
oos.writeUnshared(new Date()); // 即使相同也写入新对象
}
14. ObjectOutputStream
class Person implements Serializable {
String name;
int age;
}
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.dat"))) {
oos.writeObject(new Person("张三", 25));
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.dat"))) {
Person p = (Person) ois.readObject();
}
注意事项:
- 类必须实现
Serializable
接口 transient
字段不参与序列化- 注意
serialVersionUID
七、打印流
15. PrintStream
16. PrintWriter
增强 API:
printf(String format, Object... args)
: 格式化输出format()
: 同 printfprintln()
: 打印行(自动换行)checkError()
: 检查错误状态
高级应用:
// 示例:生成CSV文件
try (PrintWriter pw = new PrintWriter(
new BufferedWriter(
new FileWriter("data.csv")))) {
// 写入表头
pw.println("ID,Name,Salary,Department");
// 写入数据
for (Employee emp : employees) {
pw.printf("%d,%s,%.2f,%s%n", // 格式化输出
emp.getId(),
escapeCsv(emp.getName()), // 处理特殊字符
emp.getSalary(),
emp.getDepartment());
}
if (pw.checkError()) {
System.err.println("写入文件时出错");
}
}
String escapeCsv(String input) {
if (input.contains(",") || input.contains("\"")) {
return "\"" + input.replace("\"", "\"\"") + "\"";
}
return input;
}
NIO 桥接类
FileChannel (通过流获取)
// 示例:高效文件复制
try (FileInputStream fis = new FileInputStream("src.bin");
FileOutputStream fos = new FileOutputStream("dest.bin");
FileChannel src = fis.getChannel();
FileChannel dest = fos.getChannel()) {
// 三种复制方式:
// 1. 完全复制
dest.transferFrom(src, 0, src.size());
// 2. 手动控制
ByteBuffer buffer = ByteBuffer.allocateDirect(8192); // 直接缓冲区
while (src.read(buffer) != -1) {
buffer.flip(); // 切换为读模式
dest.write(buffer);
buffer.clear(); // 清空缓冲区
}
// 3. 内存映射文件(超大文件)
MappedByteBuffer map = src.map(
FileChannel.MapMode.READ_ONLY, 0, src.size());
dest.write(map);
}
总结:各类最佳使用场景
需求场景 | 推荐类 | 原因 |
---|---|---|
二进制文件读取 | BufferedInputStream + FileInputStream | 缓冲提高性能 |
二进制文件写入 | BufferedOutputStream + FileOutputStream | 缓冲提高性能 |
文本文件读取 | BufferedReader + FileReader/InputStreamReader | 支持行读取和编码控制 |
文本文件写入 | BufferedWriter + FileWriter/OutputStreamWriter | 缓冲提高性能 |
结构化数据存储 | DataInputStream/DataOutputStream | 保持数据类型 |
对象持久化 | ObjectInputStream/ObjectOutputStream | 完整对象序列化 |
日志/文本生成 | PrintWriter | 方便的格式化输出 |
超大文件处理 | FileChannel + MappedByteBuffer | 内存映射高效处理 |