Java IO 流详解
一、IO 流概述
什么是 IO
┌─────────────────────────────────────────────────────────────┐
│ I / O │
├─────────────────────────────────────────────────────────────┤
│ │
│ I = Input(输入) 从外部读取数据到程序 │
│ │
│ O = Output(输出) 从程序写入数据到外部 │
│ │
│ 程序 ←──── 输入流 ←──── 键盘/文件/网络 │
│ 程序 ────→ 输出流 ───→ 屏幕/文件/网络 │
│ │
└─────────────────────────────────────────────────────────────┘IO 流分类
┌─────────────────────────────────────────────────────────────┐
│ IO 流分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ IO 流 │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ 流向分类 数据类型 │
│ │ │ │
│ ┌─────┴─────┐ ┌──────┴──────┐ │
│ │ │ │ │ │
│ 输入流 输出流 字节流 字符流 │
│ Input Output (Stream) (Reader/ │
│ 1字节=8位 Writer) │
│ 1字符=2字节 │
│ │
└─────────────────────────────────────────────────────────────┘四大家族(抽象基类)
┌─────────────────────────────────────────────────────────────┐
│ InputStream │
│ (字节输入流抽象类) │
│ ┌────────────┬────────────┐ │
│ │ │ │ │
│ FileInput Buffered Object │
│ (文件) (缓冲) (对象) │
├─────────────────────────────────────────────────────────────┤
│ OutputStream │
│ (字节输出流抽象类) │
│ ┌────────────┬────────────┐ │
│ │ │ │ │
│ FileOutput Buffered Object │
│ (文件) (缓冲) (对象) │
├─────────────────────────────────────────────────────────────┤
│ Reader │
│ (字符输入流抽象类) │
│ ┌────────────┬────────────┐ │
│ │ │ │ │
│ FileReader Buffered InputStreamReader │
│ (文件) (缓冲) (转换) │
├─────────────────────────────────────────────────────────────┤
│ Writer │
│ (字符输出流抽象类) │
│ ┌────────────┬────────────┐ │
│ │ │ │ │
│ FileWriter Buffered OutputStreamWriter │
│ (文件) (缓冲) (转换) │
│ │
└─────────────────────────────────────────────────────────────┘二、字节流
2.1 FileInputStream(字节输入流)
构造方法
java
// 1. 通过文件路径创建
FileInputStream fis1 = new FileInputStream("C:/test.txt");
// 2. 通过 File 对象创建
File file = new File("C:/test.txt");
FileInputStream fis2 = new FileInputStream(file);
// 3. 通过文件描述符(了解即可)
FileDescriptor fd = FileDescriptor.in;
FileInputStream fis3 = new FileInputStream(fd);常用方法
java
// ┌─────────────────────────────────────────────────────────┐
// │ 读取方式 │
// └─────────────────────────────────────────────────────────┘
FileInputStream fis = new FileInputStream("C:/test.txt");
// 方式1:read() - 读取单个字节,返回 int(0-255),-1表示结束
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
// 方式2:read(byte[] b) - 读取到字节数组,返回实际读取字节数
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
// 方式3:read(byte[] b, int off, int len) - 读取指定长度
fis.read(buffer, 0, 1024);
// 其他方法
fis.available(); // 返回可读的剩余字节数
fis.skip(100); // 跳过 100 个字节
fis.mark(1000); // 标记当前位置
fis.reset(); // 重置到标记位置
fis.close(); // 关闭流
fis.close();读取示意图
┌─────────────────────────────────────────────────────────────┐
│ read() 读取过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 文件内容: [H][e][l][l][o][W][o][r][l][d] │
│ 文件指针: ↑ │
│ │
│ 第一次 read(): │
│ ├── 返回: 'H' (ASCII 72) │
│ └── 指针移动: ↓ │
│ │
│ 文件内容: [H][e][l][l][o][W][o][r][l][d] │
│ 文件指针: ↑ │
│ │
│ 第二次 read(): │
│ ├── 返回: 'e' (ASCII 101) │
│ └── 指针移动: ↓ │
│ │
│ ... │
│ │
│ 最后一次 read(): │
│ └── 返回: -1(表示已到达文件末尾) │
│ │
└─────────────────────────────────────────────────────────────┘2.2 FileOutputStream(字节输出流)
构造方法
java
// 1. 通过文件路径创建(覆盖模式)
FileOutputStream fos1 = new FileOutputStream("C:/test.txt");
// 2. 通过文件路径创建(追加模式)
FileOutputStream fos2 = new FileOutputStream("C:/test.txt", true);
// 3. 通过 File 对象创建
File file = new File("C:/test.txt");
FileOutputStream fos3 = new FileOutputStream(file);
FileOutputStream fos4 = new FileOutputStream(file, true);常用方法
java
// ┌─────────────────────────────────────────────────────────┐
// │ 写入方式 │
// └─────────────────────────────────────────────────────────┘
FileOutputStream fos = new FileOutputStream("C:/test.txt");
// 方式1:write(int b) - 写入单个字节
fos.write(65); // 写入 'A'
fos.write('B'); // 写入 'B'
// 方式2:write(byte[] b) - 写入字节数组
byte[] data = "Hello".getBytes();
fos.write(data);
// 方式3:write(byte[] b, int off, int len) - 写入部分
fos.write(data, 0, 5); // 写入前5个字节
// 其他方法
fos.flush(); // 刷新缓冲区,强制写出
fos.close(); // 关闭流(自动 flush)
fos.close();读取和写入完整示例
java
// 文件复制(字节流版)
public static void copyFile(String src, String dest) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} finally {
// 确保资源关闭
if (fis != null) fis.close();
if (fos != null) fos.close();
}
}
// JDK 7+ 推荐写法(自动关闭)
public static void copyFileModern(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}三、字符流
3.1 FileReader(字符输入流)
构造方法
java
// 1. 通过文件路径创建(使用默认编码)
FileReader fr1 = new FileReader("C:/test.txt");
// 2. 通过 File 对象创建
FileReader fr2 = new FileReader(new File("C:/test.txt"));
// 3. 指定字符集
FileReader fr3 = new FileReader("C:/test.txt", StandardCharsets.UTF_8);常用方法
java
FileReader fr = new FileReader("C:/test.txt");
// 方式1:read() - 读取单个字符,返回 int(字符),-1表示结束
int c;
while ((c = fr.read()) != -1) {
System.out.print((char) c);
}
// 方式2:read(char[] cbuf) - 读取到字符数组
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
// 方式3:read(char[] cbuf, int off, int len)
fr.read(buffer, 0, 1024);
fr.close();3.2 FileWriter(字符输出流)
构造方法
java
// 1. 通过文件路径创建(覆盖模式)
FileWriter fw1 = new FileWriter("C:/test.txt");
// 2. 通过文件路径创建(追加模式)
FileWriter fw2 = new FileWriter("C:/test.txt", true);
// 3. 通过 File 对象创建
FileWriter fw3 = new FileWriter(new File("C:/test.txt"));
FileWriter fw4 = new FileWriter(new File("C:/test.txt"), true);
// 4. 指定字符集
FileWriter fw5 = new FileWriter("C:/test.txt", StandardCharsets.UTF_8);
FileWriter fw6 = new FileWriter("C:/test.txt", true, StandardCharsets.UTF_8);常用方法
java
FileWriter fw = new FileWriter("C:/test.txt");
// 方式1:write(int c) - 写入单个字符
fw.write(65); // 写入 'A'
fw.write('B'); // 写入 'B'
// 方式2:write(String str) - 写入字符串
fw.write("Hello World");
// 方式3:write(char[] cbuf) - 写入字符数组
char[] chars = {'H', 'e', 'l', 'l', 'o'};
fw.write(chars);
// 方式4:write(String str, int off, int len) - 写入部分
fw.write("Hello", 0, 3); // 写入 "Hel"
fw.flush(); // 刷新缓冲区
fw.close(); // 关闭流3.3 字符流 vs 字节流
java
// ┌─────────────────────────────────────────────────────────┐
// │ 字节流:一次读 1 字节 │
// └─────────────────────────────────────────────────────────┘
FileInputStream fis = new FileInputStream("test.txt");
int b;
while ((b = fis.read()) != -1) {
// 处理每个字节
}
fis.close();
// ┌─────────────────────────────────────────────────────────┐
// │ 字符流:一次读 1 字符(中文占 2 字节) │
// └─────────────────────────────────────────────────────────┘
FileReader fr = new FileReader("test.txt");
int c;
while ((c = fr.read()) != -1) {
// 处理每个字符(自动处理中文)
}
fr.close();
// ┌─────────────────────────────────────────────────────────┐
// │ 中文文件读取对比 │
// └─────────────────────────────────────────────────────────┘
// 字节流读取中文(乱码风险)
FileInputStream fis2 = new FileInputStream("中文.txt");
byte[] buffer = new byte[1024];
fis2.read(buffer);
System.out.println(new String(buffer)); // 可能乱码!
// 字符流读取中文(正确)
FileReader fr2 = new FileReader("中文.txt", StandardCharsets.UTF_8);
char[] cbuf = new char[1024];
fr2.read(cbuf);
System.out.println(new String(cbuf)); // 正确显示中文四、缓冲流(处理流)
概念
┌─────────────────────────────────────────────────────────────┐
│ 普通流 vs 缓冲流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 普通流(无缓冲) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ 读 │ ───► │ 读 │ ───► │ 读 │ ───► │ 读 │ │
│ │ 1B │ │ 1B │ │ 1B │ │ 1B │ │
│ └──┬─┘ └──┬─┘ └──┬─┘ └──┬─┘ │
│ │ │ │ │ │
│ └───────────┴───────────┴───────────┘ │
│ 频繁读取硬盘,效率低 │
│ │
│ 缓冲流(带缓冲区) │
│ ┌────┐ │
│ │程序 │ ┌────┐ │
│ └──┬─┘ │ │ │
│ │ │ 缓 │ │
│ │ 一次性读│ 冲 │← 一次性写入 │
│ │ 取大量 │ 区 │ │
│ │ │ │ │
│ └────────┴────┘ │
│ 减少硬盘访问次数,效率高 │
│ │
└─────────────────────────────────────────────────────────────┘4.1 BufferedInputStream(字节缓冲输入流)
java
// 构造方法
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"));
BufferedInputStream bis2 = new BufferedInputStream(new FileInputStream("test.txt"), 8192); // 指定缓冲大小
// 方法(与 FileInputStream 相同)
bis.read(); // 读单个字节
bis.read(buffer); // 读字节数组
bis.available(); // 剩余可读字节
bis.close(); // 关闭流4.2 BufferedOutputStream(字节缓冲输出流)
java
// 构造方法
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt"));
BufferedOutputStream bos2 = new BufferedOutputStream(new FileOutputStream("test.txt"), 8192);
// 方法
bos.write(65); // 写单个字节
bos.write(buffer); // 写字节数组
bos.flush(); // 刷新缓冲区
bos.close(); // 关闭流4.3 BufferedReader(字符缓冲输入流)
java
// 构造方法
BufferedReader br = new BufferedReader(new FileReader("test.txt"));
// 特有方法:readLine() - 读取一行
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 其他方法
br.read(); // 读单个字符
br.read(buffer); // 读字符数组
br.close();4.4 BufferedWriter(字符缓冲输出流)
java
// 构造方法
BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"));
// 特有方法:newLine() - 写入换行符
bw.write("第一行");
bw.newLine(); // 换行
bw.write("第二行");
// 其他方法
bw.write("内容");
bw.flush();
bw.close();4.5 缓冲流性能对比
java
// ┌─────────────────────────────────────────────────────────┐
// │ 普通字节流文件复制 │
// └─────────────────────────────────────────────────────────┘
public static void copyWithBasicStream() throws IOException {
long start = System.currentTimeMillis();
try (FileInputStream fis = new FileInputStream("source.mp4");
FileOutputStream fos = new FileOutputStream("dest.mp4")) {
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
}
long end = System.currentTimeMillis();
System.out.println("普通流耗时: " + (end - start) + "ms");
}
// ┌─────────────────────────────────────────────────────────┐
// │ 缓冲字节流文件复制(推荐) │
// └─────────────────────────────────────────────────────────┘
public static void copyWithBufferedStream() throws IOException {
long start = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dest.mp4"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
}
long end = System.currentTimeMillis();
System.out.println("缓冲流耗时: " + (end - start) + "ms");
}
// ┌─────────────────────────────────────────────────────────┐
// │ 缓冲字符流读取文件 │
// └─────────────────────────────────────────────────────────┘
public static void readFileWithBufferedReader() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("log.txt"))) {
String line;
int count = 0;
// 按行读取
while ((line = br.readLine()) != null) {
count++;
System.out.println(count + ": " + line);
}
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ 缓冲字符流写入文件 │
// └─────────────────────────────────────────────────────────┘
public static void writeFileWithBufferedWriter() throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("第一行内容");
bw.newLine(); // 写入换行
bw.write("第二行内容");
bw.newLine();
bw.write("第三行内容");
bw.flush(); // 刷新缓冲区
}
}五、转换流
5.1 InputStreamReader(字节流 → 字符流)
┌─────────────────────────────────────────────────────────────┐
│ InputStreamReader │
├─────────────────────────────────────────────────────────────┤
│ │
│ FileInputStream InputStreamReader Reader │
│ ┌─────────┐ ┌─────────────┐ ┌─────┐ │
│ │ 字节流 │ 转换 → │ 字节→字符 │ → │字符流│ │
│ │ │ 解码 → │ (解码器) │ │ │ │
│ │ 1字节 │ │ charset │ │ 1字符│ │
│ └─────────┘ └─────────────┘ └─────┘ │
│ │
│ 用途:把字节流包装成字符流,指定字符编码 │
│ │
└─────────────────────────────────────────────────────────────┘java
// ┌─────────────────────────────────────────────────────────┐
// │ 构造方法 │
// └─────────────────────────────────────────────────────────┘
// 使用系统默认编码
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("test.txt"));
// 指定编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("test.txt"), "UTF-8");
InputStreamReader isr3 = new InputStreamReader(new FileInputStream("test.txt"), StandardCharsets.UTF_8);
// ┌─────────────────────────────────────────────────────────┐
// │ 读取方式(与 Reader 相同) │
// └─────────────────────────────────────────────────────────┘
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("中文.txt"), "GBK")) {
char[] buffer = new char[1024];
int len;
while ((len = isr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
}5.2 OutputStreamWriter(字符流 → 字节流)
┌─────────────────────────────────────────────────────────────┐
│ OutputStreamWriter │
├─────────────────────────────────────────────────────────────┤
│ │
│ Writer OutputStreamWriter OutputStream │
│ ┌─────┐ ┌─────────────┐ ┌──────────┐ │
│ │字符流│ 转换 → │ 字符→字节 │ → │ 字节流 │ │
│ │ │ 编码 → │ (编码器) │ │ │ │
│ │ 1字符│ │ charset │ │ 1字节 │ │
│ └─────┘ └─────────────┘ └──────────┘ │
│ │
│ 用途:把字符流包装成字节流,指定字符编码 │
│ │
└─────────────────────────────────────────────────────────────┘java
// ┌─────────────────────────────────────────────────────────┐
// │ 构造方法 │
// └─────────────────────────────────────────────────────────┘
// 使用系统默认编码
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("test.txt"));
// 指定编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("test.txt"), "UTF-8");
OutputStreamWriter osw3 = new OutputStreamWriter(new FileOutputStream("test.txt"), StandardCharsets.UTF_8);
// ┌─────────────────────────────────────────────────────────┐
// │ 写入方式(与 Writer 相同) │
// └─────────────────────────────────────────────────────────┘
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("中文.txt"), "GBK")) {
osw.write("你好,世界!");
osw.flush();
}5.3 转换流使用场景
java
// ┌─────────────────────────────────────────────────────────┐
// │ 场景1:按指定编码读取文件 │
// └─────────────────────────────────────────────────────────┘
// 读取 GBK 编码的文件
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("gbk.txt"), "GBK"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景2:按指定编码写入文件 │
// └─────────────────────────────────────────────────────────┘
// 写入 UTF-8 编码的文件
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("utf8.txt"), "UTF-8"))) {
bw.write("你好,世界!");
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景3:System.in 转换为字符流 │
// └─────────────────────────────────────────────────────────┘
// System.in 是 InputStream(字节流),包装成字符流
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入: ");
String input = console.readLine();
System.out.println("你输入了: " + input);六、对象流(序列化/反序列化)
6.1 概念
┌─────────────────────────────────────────────────────────────┐
│ 序列化 vs 反序列化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 序列化(Serialization) │
│ 对象 ────→ 字节序列 ────→ 文件/网络 │
│ (Serialize) │
│ │
│ 反序列化(Deserialization) │
│ 文件/网络 ────→ 字节序列 ────→ 对象 │
│ (Deserialize) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 实现 Serializable 接口才能序列化 │ │
│ │ 推荐同时定义 serialVersionUID │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.2 ObjectOutputStream(对象序列化)
java
// ┌─────────────────────────────────────────────────────────┐
// │ 创建对象输出流 │
// └─────────────────────────────────────────────────────────┘
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
// ┌─────────────────────────────────────────────────────┐
// │ 写入基本数据类型 │
// └─────────────────────────────────────────────────────┘
oos.writeInt(100);
oos.writeDouble(3.14);
oos.writeBoolean(true);
oos.writeUTF("Hello");
// ┌─────────────────────────────────────────────────────┐
// │ 写入对象 │
// └─────────────────────────────────────────────────────┘
User user = new User("Alice", 25);
oos.writeObject(user);
oos.flush();
}6.3 ObjectInputStream(对象反序列化)
java
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.dat"))) {
// 读取顺序必须与写入顺序一致
int num = ois.readInt(); // 100
double pi = ois.readDouble(); // 3.14
boolean flag = ois.readBoolean(); // true
String str = ois.readUTF(); // "Hello"
// 读取对象
User user = (User) ois.readObject();
System.out.println(user.getName()); // "Alice"
System.out.println(user.getAge()); // 25
}6.4 Serializable 接口
java
// ┌─────────────────────────────────────────────────────────┐
// │ 必须实现 Serializable 才能序列化 │
// └─────────────────────────────────────────────────────────┘
public class User implements Serializable {
// 推荐:声明序列化版本号
private static final long serialVersionUID = 1L;
// 序列化字段
private String name;
private int age;
// transient 修饰的字段不参与序列化
private transient String password;
// static 字段不参与序列化
private static String className = "User";
// ┌─────────────────────────────────────────────────────┐
// │ 构造方法(反序列化时调用) │
// └─────────────────────────────────────────────────────┘
public User() {
System.out.println("反序列化时调用");
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getters and setters
}6.5 序列化注意事项
java
// ┌─────────────────────────────────────────────────────────┐
// │ 1. serialVersionUID 的作用 │
// └─────────────────────────────────────────────────────────┘
// 类修改后,serialVersionUID 会变化
// 如果与文件中保存的版本不一致,会抛出 InvalidClassException
// 建议始终声明
private static final long serialVersionUID = 1L;
// ┌─────────────────────────────────────────────────────────┐
// │ 2. transient 关键字 │
// └─────────────────────────────────────────────────────────┘
public class User implements Serializable {
private String name;
private transient String password; // 密码不会被序列化
// 反序列化后,password = null
}
// ┌─────────────────────────────────────────────────────────┐
// │ 3. static 字段不参与序列化 │
// └─────────────────────────────────────────────────────────┘
public class User implements Serializable {
private String name;
private static int count; // 不参与序列化
// 反序列化时,count = 类中的当前值(默认值0)
}
// ┌─────────────────────────────────────────────────────────┐
// │ 4. 父类实现 Serializable,子类自动可序列化 │
// └─────────────────────────────────────────────────────────┘
public class Person implements Serializable { }
public class User extends Person { } // User 也可序列化
// ┌─────────────────────────────────────────────────────────┐
// │ 5. 集合序列化 │
// └─────────────────────────────────────────────────────────┘
List<User> users = new ArrayList<>();
users.add(new User("Alice", 25));
users.add(new User("Bob", 30));
// 整个集合作为对象序列化
oos.writeObject(users);
// 反序列化
List<User> restored = (List<User>) ois.readObject();七、打印流
7.1 PrintStream(字节打印流)
java
// ┌─────────────────────────────────────────────────────────┐
// │ 创建 PrintStream │
// └─────────────────────────────────────────────────────────┘
// 输出到控制台(默认)
PrintStream ps1 = System.out;
// 输出到文件
PrintStream ps2 = new PrintStream("output.txt");
// 自动刷新
PrintStream ps3 = new PrintStream(new FileOutputStream("output.txt"), true);
// ┌─────────────────────────────────────────────────────────┐
// │ 打印方法 │
// └─────────────────────────────────────────────────────────┘
PrintStream ps = new PrintStream("output.txt");
ps.print(100); // 不换行
ps.println(100); // 换行
ps.println("Hello");
ps.printf("姓名: %s, 年龄: %d", "Alice", 25); // 格式化输出
ps.flush();
ps.close();
// ┌─────────────────────────────────────────────────────────┐
// │ 重定向 System.out │
// └─────────────────────────────────────────────────────────┘
// 保存原输出流
PrintStream original = System.out;
// 重定向到文件
System.setOut(new PrintStream("log.txt"));
System.out.println("这条会写到文件");
// 恢复控制台输出
System.setOut(original);
System.out.println("这条输出到控制台");7.2 PrintWriter(字符打印流)
java
// ┌─────────────────────────────────────────────────────────┐
// │ 创建 PrintWriter │
// └─────────────────────────────────────────────────────────┘
// 输出到文件
PrintWriter pw1 = new PrintWriter("output.txt");
// 包装字节流(自动刷新)
PrintWriter pw2 = new PrintWriter(new OutputStreamWriter(System.out), true);
// ┌─────────────────────────────────────────────────────────┐
// │ 打印方法 │
// └─────────────────────────────────────────────────────────┘
try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) {
pw.print(100);
pw.println("Hello");
pw.println("World");
pw.printf("%.2f", 3.14159); // 3.14
pw.printf("%04d", 5); // 0005
pw.flush();
}八、数据流(DataInputStream/DataOutputStream)
概念
┌─────────────────────────────────────────────────────────────┐
│ DataOutputStream │
├─────────────────────────────────────────────────────────────┤
│ │
│ 可以直接写入基本数据类型,而不需要手动转换 │
│ │
│ writeInt(100) → 写入 4 字节 │
│ writeDouble(3.14) → 写入 8 字节 │
│ writeUTF("Hello") → 写入长度+字符串 │
│ │
│ 必须配合 DataInputStream 读取 │
│ │
└─────────────────────────────────────────────────────────────┘代码示例
java
// ┌─────────────────────────────────────────────────────────┐
// │ 写入基本数据类型 │
// └─────────────────────────────────────────────────────────┘
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.bin"))) {
dos.writeInt(100); // 整数
dos.writeLong(1000L); // 长整数
dos.writeDouble(3.14); // 双精度
dos.writeBoolean(true); // 布尔值
dos.writeChar('A'); // 字符
dos.writeUTF("Hello"); // UTF字符串
dos.flush();
}
// ┌─────────────────────────────────────────────────────────┐
// │ 读取基本数据类型 │
// └─────────────────────────────────────────────────────────┘
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.bin"))) {
int num = dis.readInt(); // 100
long bigNum = dis.readLong(); // 1000L
double pi = dis.readDouble(); // 3.14
boolean flag = dis.readBoolean(); // true
char c = dis.readChar(); // 'A'
String str = dis.readUTF(); // "Hello"
}九、装饰器模式
原理
┌─────────────────────────────────────────────────────────────┐
│ 装饰器模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Component(组件接口) │
│ △ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ │ 真实组件 │ ←─ FileInputStream │
│ │ Filter │ ←─ BufferedInputStream(装饰器) │
│ └─────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ InputStream │ │
│ │ │ │ │
│ │ FileInputStream (真实组件,直接操作文件) │ │
│ │ │ │ │
│ │ BufferedInputStream (装饰器,添加缓冲功能) │ │
│ │ │ │ │
│ │ DataInputStream (装饰器,添加数据类型读取) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 特点: │
│ • 装饰器和被装饰对象实现同一接口 │
│ • 装饰器持有被装饰对象的引用 │
│ • 可以无限嵌套装饰 │
│ │
└─────────────────────────────────────────────────────────────┘代码示例
java
// ┌─────────────────────────────────────────────────────────┐
// │ 逐层装饰 │
// └─────────────────────────────────────────────────────────┘
// 基础:FileInputStream(读取文件)
InputStream is1 = new FileInputStream("test.txt");
// 第1层装饰:BufferedInputStream(添加缓冲)
InputStream is2 = new BufferedInputStream(is1);
// 第2层装饰:DataInputStream(添加数据类型读取)
DataInputStream is3 = new DataInputStream(is2);
// 第3层装饰:自定义加密流
InputStream is4 = new CryptoInputStream(is3);
// 使用
is4.read(); // 数据流经:Crypto → Data → Buffer → File
// ┌─────────────────────────────────────────────────────────┐
// │ 自定义装饰器示例 │
// └─────────────────────────────────────────────────────────┘
// 继承 FilterInputStream 实现自定义装饰器
public class UpperCaseInputStream extends FilterInputStream {
public UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? -1 : Character.toUpperCase((char) c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; i++) {
b[i] = (byte) Character.toUpperCase((char) b[i]);
}
return result;
}
}
// 使用
try (InputStream is = new UpperCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")))) {
int b;
while ((b = is.read()) != -1) {
System.out.print((char) b); // 输出的字符都是大写
}
}十、IO 流完整汇总
分类速查表
| 类型 | 输入流 | 输出流 | 说明 |
|---|---|---|---|
| 字节流 | InputStream | OutputStream | 处理字节(图片、音视频) |
| 节点流 | FileInputStream | FileOutputStream | 直接操作文件 |
| 缓冲流 | BufferedInputStream | BufferedOutputStream | 带缓冲区,高效 |
| 数据流 | DataInputStream | DataOutputStream | 读写基本数据类型 |
| 对象流 | ObjectInputStream | ObjectOutputStream | 序列化/反序列化 |
| 打印流 | - | PrintStream / PrintWriter | 格式化输出 |
| 字符流 | Reader | Writer | 处理字符(文本) |
| 节点流 | FileReader | FileWriter | 直接操作文件 |
| 缓冲流 | BufferedReader | BufferedWriter | 按行读写,高效 |
| 转换流 | InputStreamReader | OutputStreamWriter | 字节↔字符转换 |
适用场景
java
// ┌─────────────────────────────────────────────────────────┐
// │ 场景1:读写文本文件 │
// └─────────────────────────────────────────────────────────┘
// 字符流(推荐)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景2:读写二进制文件(图片、音视频) │
// └─────────────────────────────────────────────────────────┘
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("image.png"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.png"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景3:序列化对象 │
// └─────────────────────────────────────────────────────────┘
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User user = new User("Alice", 25);
oos.writeObject(user); // 序列化
User restored = (User) ois.readObject(); // 反序列化
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景4:指定编码读写文件 │
// └─────────────────────────────────────────────────────────┘
// 读取 GBK 文件
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("gbk.txt"), "GBK"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 写入 UTF-8 文件
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("utf8.txt"), "UTF-8"))) {
bw.write("中文内容");
}
// ┌─────────────────────────────────────────────────────────┐
// │ 场景5:System.in 读取控制台输入 │
// └─────────────────────────────────────────────────────────┘
// 方法1:BufferedReader
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
String input = br.readLine();
System.out.println("你输入了: " + input);
}
// 方法2:Scanner
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// ┌─────────────────────────────────────────────────────────┐
// │ 场景6:读写 Properties 配置文件 │
// └─────────────────────────────────────────────────────────┘
Properties props = new Properties();
// 读取
try (InputStream in = new FileInputStream("config.properties")) {
props.load(in);
String value = props.getProperty("username");
}
// 写入
try (OutputStream out = new FileOutputStream("config.properties")) {
props.setProperty("username", "admin");
props.store(out, "配置文件");
}十一、try-with-resources 自动关闭
原理
java
// ┌─────────────────────────────────────────────────────────┐
// │ 传统写法(繁琐) │
// └─────────────────────────────────────────────────────────┘
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("source.txt");
fos = new FileOutputStream("dest.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 必须手动关闭,容易忘记
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// ┌─────────────────────────────────────────────────────────┐
// │ try-with-resources 写法(简洁,推荐) │
// └─────────────────────────────────────────────────────────┘
try (FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("dest.txt")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 自动关闭,无需 finally
// ┌─────────────────────────────────────────────────────────┐
// │ 多资源关闭 │
// └─────────────────────────────────────────────────────────┘
try (
BufferedReader br = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"));
) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} // 自动关闭 br 和 bw实现 Closeable 接口
java
// ┌─────────────────────────────────────────────────────────┐
// │ 自定义资源类实现 AutoCloseable │
// └─────────────────────────────────────────────────────────┘
public class MyResource implements AutoCloseable {
private String name;
public MyResource(String name) {
this.name = name;
System.out.println("打开资源: " + name);
}
public void use() {
System.out.println("使用资源: " + name);
}
@Override
public void close() {
System.out.println("关闭资源: " + name);
}
}
// 使用
try (MyResource r1 = new MyResource("资源1");
MyResource r2 = new MyResource("资源2")) {
r1.use();
r2.use();
} // 自动调用 close(),先关闭 r2,再关闭 r1
// 输出:
// 打开资源: 资源1
// 打开资源: 资源2
// 使用资源: 资源1
// 使用资源: 资源2
// 关闭资源: 资源2
// 关闭资源: 资源1十二、总结
IO 流选择指南
┌─────────────────────────────────────────────────────────────┐
│ IO 选择指南 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 读取文本文件? │
│ ├── BufferedReader + FileReader(推荐) │
│ └── InputStreamReader + FileInputStream(需指定编码) │
│ │
│ 写入文本文件? │
│ ├── BufferedWriter + FileWriter(推荐) │
│ └── OutputStreamWriter + FileOutputStream(需指定编码) │
│ │
│ 读写二进制文件(图片、音视频)? │
│ └── BufferedInputStream / BufferedOutputStream │
│ │
│ 序列化对象? │
│ └── ObjectOutputStream / ObjectInputStream │
│ │
│ 格式化输出? │
│ └── PrintWriter / PrintStream │
│ │
│ 指定字符编码? │
│ └── InputStreamReader / OutputStreamWriter │
│ │
└─────────────────────────────────────────────────────────────┘最佳实践
java
// ┌─────────────────────────────────────────────────────────┐
// │ 1. 始终使用 try-with-resources │
// └─────────────────────────────────────────────────────────┘
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// ...
}
// ┌─────────────────────────────────────────────────────────┐
// │ 2. 使用缓冲流提升性能 │
// └─────────────────────────────────────────────────────────┘
// 普通流:每次 read() 都访问硬盘
// 缓冲流:一次性读入大量数据到内存,减少硬盘访问
// ┌─────────────────────────────────────────────────────────┐
// │ 3. 处理中文使用字符流或指定编码 │
// └─────────────────────────────────────────────────────────┘
// 字符流(自动处理)
try (BufferedReader br = new BufferedReader(new FileReader("中文.txt"))) {
// ...
}
// 或指定编码
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("中文.txt"), "UTF-8"))) {
// ...
}
// ┌─────────────────────────────────────────────────────────┐
// │ 4. 序列化时注意 │
// └─────────────────────────────────────────────────────────┘
// • 实现 Serializable 接口
// • 声明 serialVersionUID
// • 不需要序列化的字段用 transient
// • 不要修改类结构,否则反序列化失败相关标签
#Java #IO #InputStream #OutputStream #Reader #Writer #BufferedReader #BufferedWriter #ObjectInputStream #ObjectOutputStream #序列化 #反序列化 #try-with-resources