Skip to content

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 流完整汇总

分类速查表

类型输入流输出流说明
字节流InputStreamOutputStream处理字节(图片、音视频)
节点流FileInputStreamFileOutputStream直接操作文件
缓冲流BufferedInputStreamBufferedOutputStream带缓冲区,高效
数据流DataInputStreamDataOutputStream读写基本数据类型
对象流ObjectInputStreamObjectOutputStream序列化/反序列化
打印流-PrintStream / PrintWriter格式化输出
字符流ReaderWriter处理字符(文本)
节点流FileReaderFileWriter直接操作文件
缓冲流BufferedReaderBufferedWriter按行读写,高效
转换流InputStreamReaderOutputStreamWriter字节↔字符转换

适用场景

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

Knowledge4J — Java 知识库