Java IO流详解
目录
2. FileInputStream & FileOutputStream
1. BufferedInputStream & BufferedOutputStream
2. BufferedReader & BufferedWriter
1. DataInputStream & DataOutputStream
1. ByteArrayInputStream & ByteArrayOutputStream
2. try-with-resources(推荐,Java 7+)
Java IO流详解
Java的IO(Input/Output)是处理数据输入输出的核心机制。IO流按照不同维度可以分为:字节流与字符流、输入流与输出流、节点流与处理流。本文将系统梳理Java传统IO流的体系结构和核心用法。
一、IO流体系总览
1. 四大抽象基类
Java IO流的顶层是四个抽象类:
| 类型 | 字节流 | 字符流 |
|---|---|---|
| 输入流 | InputStream |
Reader |
| 输出流 | OutputStream |
Writer |
所有IO流类都继承自这四个基类之一。
2. IO流家族图谱
IO流
├── 字节流(处理二进制数据:图片、视频、音频等)
│ ├── InputStream(字节输入流)
│ │ ├── FileInputStream —— 文件字节输入流
│ │ ├── BufferedInputStream —— 缓冲字节输入流
│ │ ├── ByteArrayInputStream —— 字节数组输入流
│ │ ├── DataInputStream —— 数据输入流
│ │ └── ObjectInputStream —— 对象输入流(反序列化)
│ └── OutputStream(字节输出流)
│ ├── FileOutputStream —— 文件字节输出流
│ ├── BufferedOutputStream —— 缓冲字节输出流
│ ├── ByteArrayOutputStream —— 字节数组输出流
│ ├── DataOutputStream —— 数据输出流
│ └── ObjectOutputStream —— 对象输出流(序列化)
│
└── 字符流(处理文本数据:txt、json、xml等)
├── Reader(字符输入流)
│ ├── FileReader —— 文件字符输入流
│ ├── BufferedReader —— 缓冲字符输入流
│ ├── InputStreamReader —— 转换流(字节→字符)
│ └── StringReader —— 字符串输入流
└── Writer(字符输出流)
├── FileWriter —— 文件字符输出流
├── BufferedWriter —— 缓冲字符输出流
├── OutputStreamWriter —— 转换流(字符→字节)
└── StringWriter —— 字符串输出流
二、字节流
字节流以字节(8bit)为单位处理数据,可以处理任意类型的文件。
1. InputStream & OutputStream
InputStream 是所有字节输入流的父类,核心方法:
public abstract class InputStream implements Closeable {
// 读取一个字节,返回0-255的int值,到达末尾返回-1
public abstract int read() throws IOException;
// 读取数据到字节数组,返回实际读取的字节数,到达末尾返回-1
public int read(byte b[]) throws IOException { ... }
// 读取数据到字节数组的指定位置
public int read(byte b[], int off, int len) throws IOException { ... }
// 跳过n个字节
public long skip(long n) throws IOException { ... }
// 关闭流
public void close() throws IOException { ... }
}
OutputStream 是所有字节输出流的父类,核心方法:
public abstract class OutputStream implements Closeable, Flushable {
// 写出一个字节
public abstract void write(int b) throws IOException;
// 写出字节数组
public void write(byte b[]) throws IOException { ... }
// 写出字节数组的指定部分
public void write(byte b[], int off, int len) throws IOException { ... }
// 刷新缓冲区,强制写出
public void flush() throws IOException { ... }
// 关闭流
public void close() throws IOException { ... }
}
2. FileInputStream & FileOutputStream
文件字节流是最常用的节点流,直接与文件关联。
// 文件复制示例
public class FileCopy {
public static void main(String[] args) {
// try-with-resources 自动关闭流
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件复制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意事项:
FileOutputStream默认覆盖模式,构造时传true可追加写入:new FileOutputStream("file.txt", true)- 每次
write()后建议调用flush()或使用缓冲流 - 必须关闭流释放系统资源,推荐
try-with-resources
三、字符流
字符流以字符(16bit)为单位处理数据,自动处理字符编码,适合处理文本文件。
1. Reader & Writer
Reader 是所有字符输入流的父类:
public abstract class Reader implements Readable, Closeable {
// 读取一个字符,返回0-65535的int值,到达末尾返回-1
public int read() throws IOException { ... }
// 读取到字符数组
public int read(char cbuf[]) throws IOException { ... }
// 读取到字符数组的指定位置
public abstract int read(char cbuf[], int off, int len) throws IOException;
}
Writer 是所有字符输出流的父类:
public abstract class Writer implements Appendable, Closeable, Flushable {
// 写出一个字符
public void write(int c) throws IOException { ... }
// 写出字符数组
public void write(char cbuf[]) throws IOException { ... }
// 写出字符串
public void write(String str) throws IOException { ... }
// Writer特有:支持链式调用
public Writer append(CharSequence csq) throws IOException { ... }
}
2. FileReader & FileWriter
文件字符流,简化了文本文件的读写操作。
// 读取文本文件
public class ReadTextFile {
public static void main(String[] args) {
try (FileReader reader = new FileReader("hello.txt")) {
char[] buffer = new char[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = reader.read(buffer)) != -1) {
sb.append(buffer, 0, len);
}
System.out.println(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 写入文本文件
public class WriteTextFile {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, Java IO!\n");
writer.write("字符流写入文本非常方便");
// FileWriter内部使用默认字符编码
} catch (IOException e) {
e.printStackTrace();
}
}
}
字节流 vs 字符流选择:
- 处理二进制文件(图片、视频、压缩包)→ 字节流
- 处理文本文件(txt、json、xml)→ 字符流
- 不确定时 → 字节流(更通用)
四、缓冲流
缓冲流为底层流添加缓冲区,减少实际IO次数,显著提升性能。缓冲流是处理流(包装其他流)。
1. BufferedInputStream & BufferedOutputStream
// 使用缓冲字节流复制文件(性能更好)
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.mp4"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// bos.flush(); // close()会自动调用flush()
}
2. BufferedReader & BufferedWriter
// 按行读取文本文件
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 按行写入文本文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("第一行");
bw.newLine(); // 跨平台换行
bw.write("第二行");
bw.newLine();
}
BufferedReader 还提供了一个常用方法:
// 从控制台读取用户输入(传统方式)
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入: ");
String input = reader.readLine();
缓冲流性能对比:
| 方式 | 复制100MB文件耗时(参考值) |
|---|---|
| 无缓冲字节流 | ~30秒 |
| 8KB缓冲字节流 | ~0.1秒 |
| 缓冲字节流(默认8KB) | ~0.1秒 |
五、转换流
转换流是字节流和字符流之间的桥梁,可以指定字符编码。
1. InputStreamReader(字节→字符)
// 读取GBK编码的文件
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk_file.txt"), "GBK");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
2. OutputStreamWriter(字符→字节)
// 以UTF-8编码写入文件
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8_file.txt"), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw)) {
bw.write("你好,世界!");
}
为什么需要转换流?
FileReader / FileWriter 使用平台默认编码,无法指定编码。需要处理特定编码时,必须使用转换流。
// FileReader 等价于(但无法指定编码):
new InputStreamReader(new FileInputStream("file.txt"), Charset.defaultCharset())
六、数据流与对象流
1. DataInputStream & DataOutputStream
数据流可以直接读写Java基本数据类型(int、double、boolean等)。
// 写入基本类型
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.bin"))) {
dos.writeInt(100);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("Hello");
}
// 读取基本类型(必须按写入顺序读取)
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.bin"))) {
int i = dis.readInt(); // 100
double d = dis.readDouble(); // 3.14
boolean b = dis.readBoolean(); // true
String s = dis.readUTF(); // "Hello"
}
2. 对象序列化与反序列化
对象流可以将Java对象转换为字节序列(序列化),也可以从字节序列恢复对象(反序列化)。
// 实现Serializable接口
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号
private String name;
private int age;
private transient String password; // transient:不参与序列化
// 构造方法、getter、setter省略
}
// 序列化:对象 → 文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
User user = new User("张三", 25, "123456");
oos.writeObject(user);
}
// 反序列化:文件 → 对象
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.dat"))) {
User user = (User) ois.readObject();
System.out.println(user.getName()); // 张三
System.out.println(user.getPassword()); // null(被transient修饰)
}
序列化注意事项:
- 类必须实现
Serializable接口(标记接口,无方法) - 建议显式声明
serialVersionUID,避免版本兼容问题 static字段不参与序列化transient字段不参与序列化- 父类如果没有实现
Serializable,父类的字段不会被序列化
七、其他常用流
1. ByteArrayInputStream & ByteArrayOutputStream
在内存中操作字节数组,常用于数据转换和临时存储。
// 将字节数组转为流处理
byte[] data = "Hello".getBytes();
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
int b;
while ((b = bais.read()) != -1) {
System.out.print((char) b); // H e l l o
}
}
// 收集输出到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("Hello".getBytes());
baos.write(" World".getBytes());
byte[] result = baos.toByteArray(); // "Hello World"
2. PrintStream & PrintWriter
打印流提供了方便的 print() / println() 方法。
// System.out 就是 PrintStream
System.out.println("Hello");
// 打印到文件
try (PrintWriter pw = new PrintWriter(new FileWriter("log.txt"))) {
pw.println("日志信息");
pw.printf("用户: %s, 年龄: %d%n", "张三", 25);
}
3. 标准输入输出流
// 标准输入:System.in(InputStream)
// 标准输出:System.out(PrintStream)
// 标准错误:System.err(PrintStream)
// 重定向标准输出到文件
PrintStream ps = new PrintStream("output.log");
System.setOut(ps); // 之后的System.out输出到文件
System.out.println("这会写入文件");
八、try-with-resources 与异常处理
1. 传统方式(繁琐)
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用流...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. try-with-resources(推荐,Java 7+)
// 实现了AutoCloseable接口的资源可以自动关闭
try (FileInputStream fis = new FileInputStream("file.txt");
FileOutputStream fos = new FileOutputStream("copy.txt")) {
// 使用流...
} catch (IOException e) {
e.printStackTrace();
}
// 流自动关闭,无需finally
注意: 多个资源按声明的逆序关闭(先关fos,再关fis)。
九、设计思想:装饰器模式
Java IO流的设计大量使用了装饰器模式:
// 节点流(被装饰者):直接操作数据源
FileInputStream fis = new FileInputStream("file.txt");
// 处理流(装饰者):增强功能
BufferedInputStream bis = new BufferedInputStream(fis); // 添加缓冲
DataInputStream dis = new DataInputStream(bis); // 添加读取基本类型能力
装饰器模式的好处:
- 灵活组合功能(缓冲 + 编码转换 + 数据类型)
- 遵循开闭原则(扩展功能不修改原有代码)
- 可以嵌套多层装饰
// 实际开发中的典型组合
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), "UTF-8"));
十、面试常见问题
1. IO流的分类?
| 维度 | 分类 |
|---|---|
| 数据单位 | 字节流、字符流 |
| 流向 | 输入流、输出流 |
| 角色 | 节点流(直接操作数据源)、处理流(包装其他流) |
2. 字节流和字符流的区别?
- 字节流以字节为单位,字符流以字符为单位
- 字节流可以处理任意文件,字符流只能处理文本
- 字符流有缓冲区(内部维护char[]),字节流无缓冲区
- 字符流自动处理编码转换,字节流需要手动处理
3. BIO、NIO、AIO的区别?
- BIO:同步阻塞,一个连接一个线程,适合连接数少的场景
- NIO:同步非阻塞,基于Channel/Buffer/Selector,一个线程管理多个连接
- AIO:异步非阻塞,基于回调通知,适合连接数多且IO操作重的场景
4. 什么是缓冲流?为什么能提高性能?
缓冲流在内部维护一个缓冲区(默认8KB),减少实际的系统IO调用次数。每次read/write先操作缓冲区,缓冲区满或手动flush时才进行真正的IO操作。
5. 序列化和反序列化是什么?
- 序列化:将对象转换为字节序列,便于存储或传输
- 反序列化:将字节序列恢复为对象
- 实现
Serializable接口,使用ObjectOutputStream/ObjectInputStream transient修饰的字段不参与序列化
总结
| 场景 | 推荐使用的流 |
|---|---|
| 复制文件(二进制) | BufferedInputStream + BufferedOutputStream |
| 读写文本文件 | BufferedReader + BufferedWriter |
| 指定编码读写 | InputStreamReader + OutputStreamWriter |
| 读写基本类型 | DataInputStream + DataOutputStream |
| 对象持久化 | ObjectOutputStream + ObjectInputStream |
| 控制台输入 | BufferedReader(new InputStreamReader(System.in)) |
核心原则:
- 明确数据类型:二进制用字节流,文本用字符流
- 使用缓冲流提升性能
- 始终用
try-with-resources确保资源释放 - 需要指定编码时使用转换流
更多推荐
所有评论(0)