背景

本文是《Java 后端从小白到大神》修仙系列第九篇,正式进入Java后端世界,本篇文章主要聊Java基础。若想详细学习请点击首篇博文,我们开始把。

文章概览

  1. 核心工具类(Java IO)

核心工具类(续)

1. Java IO

1. OutputStream

graph LR
    A[OutputStream] --> B[节点流]
    A --> C[处理流]
    B --> D[FileOutputStream<写入文件字节>]
    B --> E[ByteArrayOutputStream<写入字节数组>]
    B --> F[PipedOutputStream<用于线程间通信>]

    C --> H[FilterOutputStream<装饰器模式基类>]
    H --> I[BufferedOutputStream<缓冲提升性能>]
    H --> J[DataOutputStream<写入基本数据类型(int, double)>]
    C --> L[ObjectOutputStream<写入反序列化对象>]

1. FileOutputStream

场景:将字节数据写入文件(如图片、音频等非文本文件)。
技巧:使用 try-with-resources 自动关闭流,避免资源泄漏;写入后调用 flush() 确保数据持久化。

1
2
3
4
5
6
7
try (FileOutputStream fos = new FileOutputStream("output.bin")) {
    byte[] data = "Hello FileOutputStream".getBytes();
    fos.write(data); // 写入字节数组
    fos.flush();     // 强制刷新缓冲区到磁盘
} catch (IOException e) {
    e.printStackTrace();
}

2. ByteArrayOutputStream

场景:在内存中动态构建字节数据,无需直接操作文件。
技巧:通过 toByteArray() 获取最终字节数组,适用于生成动态内容(如 PDF、图片处理)。

1
2
3
4
5
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("Hello ".getBytes());
baos.write("ByteArrayOutputStream".getBytes());
byte[] result = baos.toByteArray(); // 获取内存中的字节数组
System.out.println(new String(result)); // 输出: Hello ByteArrayOutputStream

3. PipedOutputStream

场景:与 PipedInputStream 配合,实现线程间管道通信(生产者-消费者模型)。
技巧:必须显式调用 connect() 连接管道,并在使用后关闭流。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream();
pos.connect(pis); // 建立管道连接

// 生产者线程写入数据
new Thread(() -> {
    try {
        pos.write("Data from PipedOutputStream".getBytes());
        pos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

// 消费者线程读取数据
new Thread(() -> {
    try {
        int byteData;
        while ((byteData = pis.read()) != -1) {
            System.out.print((char) byteData); // 输出: Data from PipedOutputStream
        }
        pis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

4. BufferedOutputStream

场景:为其他输出流提供缓冲,减少磁盘 I/O 操作次数,提升写入效率。
技巧:默认缓冲区大小 8KB,可自定义大小(如 new BufferedOutputStream(fos, 8192))。

1
2
3
4
5
6
7
8
9
try (FileOutputStream fos = new FileOutputStream("largefile.bin");
     BufferedOutputStream bos = new BufferedOutputStream(fos)) { // 包装文件流
    byte[] data = new byte[1024];
    // 填充 data...
    bos.write(data);  // 写入缓冲区
    bos.flush();      // 强制刷新缓冲区到磁盘
} catch (IOException e) {
    e.printStackTrace();
}

5. DataOutputStream

场景:将基本数据类型(如 int, double)以二进制格式写入文件或网络。
技巧:需与 DataInputStream 配对使用,确保读写顺序一致。

1
2
3
4
5
6
7
8
try (FileOutputStream fos = new FileOutputStream("data.bin");
     DataOutputStream dos = new DataOutputStream(fos)) {
    dos.writeInt(100);      // 写入 int
    dos.writeDouble(3.14);  // 写入 double
    dos.writeUTF("Hello");  // 写入 UTF-8 字符串
} catch (IOException e) {
    e.printStackTrace();
}

6. ObjectOutputStream

场景:序列化对象到文件或网络(需实现 Serializable 接口)。
技巧:使用 transient 关键字标记不序列化的字段,避免敏感数据泄露。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    transient String password; // 不序列化
    public User(String name) { this.name = name; }
}

try (FileOutputStream fos = new FileOutputStream("user.bin");
     ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    User user = new User("Alice");
    oos.writeObject(user); // 序列化对象
} catch (IOException e) {
    e.printStackTrace();
}

总结

核心场景 关键技巧
FileOutputStream 写入文件原始字节 使用 try-with-resources 关闭流
ByteArrayOutputStream 动态构建内存字节数据 通过 toByteArray() 获取结果
PipedOutputStream 线程间管道通信 必须连接 PipedInputStream
BufferedOutputStream 提升写入效率 默认 8KB 缓冲,可调整大小
DataOutputStream 写入基本数据类型 DataInputStream 配对使用
ObjectOutputStream 序列化对象到流 对象需实现 Serializable 接口

通过合理选择输出流类型,可以高效处理数据持久化、网络传输和内存操作等场景。

2. Reader

graph LR
    A[Reader] --> B[节点流]
    A --> C[处理流]

    B --> D[FileReader<读取文件字符,默认编码>]
    B --> E[StringReader<读取字符串字符>]
    B --> F[PipedReader<用于线程间通信>]
    B --> G[CharArrayReader<读取字符数组>]
    B --> H[InputStreamReader<桥接流字节转字符,指定编码>]

    C --> I[BufferedReader<缓冲提升性能+换行支持>]
    C --> J[LineNumberReader<行号支持>]
    C --> K[PushbackReader<支持回退字符>]

1. FileReader

场景:用于读取字符文件的节点流,继承自 InputStreamReader,默认使用系统字符编码。
技巧:适合读取文本文件,但推荐明确指定编码,但FileReader不可以指定编码(如 UTF-8)。

1
2
3
4
5
6
try (FileReader reader = new FileReader("text.txt")) {
    int charData;
    while ((charData = reader.read()) != -1) { // 逐字符读取
        System.out.print((char) charData);
    }
}

2. StringReader

场景:将字符串作为字符流读取的内存流
技巧:适用于测试或动态生成文本内容的场景。

1
2
3
4
5
6
7
String data = "Hello StringReader";
try (StringReader reader = new StringReader(data)) {
    int charData;
    while ((charData = reader.read()) != -1) {
        System.out.print((char) charData); // 输出: Hello StringReader
    }
}

3. PipedReader

场景:与 PipedWriter 配合实现线程间字符数据的管道通信
技巧:需调用 connect() 方法建立管道连接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
reader.connect(writer);

// 生产者线程写入数据
new Thread(() -> {
    try (writer) {
        writer.write("Data from PipedWriter");
    } catch (IOException e) { e.printStackTrace(); }
}).start();

// 消费者线程读取数据
new Thread(() -> {
    try (reader) {
        int charData;
        while ((charData = reader.read()) != -1) {
            System.out.print((char) charData); // 输出: Data from PipedWriter
        }
    } catch (IOException e) { e.printStackTrace(); }
}).start();

4. CharArrayReader

场景:从字符数组中读取数据的内存流
技巧:适用于需要复用字符数组的场景。

1
2
3
4
5
6
7
char[] data = {'J', 'a', 'v', 'a'};
try (CharArrayReader reader = new CharArrayReader(data)) {
    int charData;
    while ((charData = reader.read()) != -1) {
        System.out.print((char) charData); // 输出: Java
    }
}

5. InputStreamReader

场景:字节流通向字符流的桥梁,可指定字符编码(如 UTF-8)。
技巧:常用于处理非默认编码的文本文件。

1
2
3
4
5
6
7
try (InputStream is = new FileInputStream("data.txt");
     InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
    int charData;
    while ((charData = reader.read()) != -1) {
        System.out.print((char) charData);
    }
}

6. BufferedReader

场景:为其他 Reader 提供缓冲的处理流,支持按行读取。
技巧:默认缓冲区大小 8KB,推荐所有文本读取操作优先使用。

1
2
3
4
5
6
try (BufferedReader reader = new BufferedReader(new FileReader("text.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) { // 按行读取
        System.out.println(line);
    }
}

7. LineNumberReader

场景:继承自 BufferedReader,可跟踪当前读取的行号。
技巧:调试时定位文本错误位置。

1
2
3
4
5
6
try (LineNumberReader reader = new LineNumberReader(new FileReader("text.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println("Line " + reader.getLineNumber() + ": " + line);
    }
}

8. PushbackReader

场景:允许将已读取的字符回退到流中的处理流
技巧:解析复杂文本时回退字符(如处理自定义分隔符)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
String data = "Hello*World";
try (StringReader sr = new StringReader(data);
     PushbackReader reader = new PushbackReader(sr)) {
    int charData;
    while ((charData = reader.read()) != -1) {
        if (charData == '*') {
            reader.unread(' '); // 将空格回退到流中
            charData = reader.read(); // 重新读取空格
        }
        System.out.print((char) charData); // 输出: Hello World
    }
}

总结

核心场景 关键特性
FileReader 读取文本文件 默认系统编码,适合简单场景
StringReader 内存字符串处理 轻量级,无需物理文件
PipedReader 线程间字符通信 需与 PipedWriter 配合
CharArrayReader 从字符数组读取 复用现有数组
InputStreamReader 字节流转字符流(指定编码) 支持自定义编码(如 UTF-8)
BufferedReader 高效读取文本(推荐默认使用) 支持按行读取,缓冲提升性能
LineNumberReader 跟踪行号 调试或日志分析
PushbackReader 回退字符重新解析 灵活处理复杂文本格式

最佳实践

  • 优先使用 BufferedReader 包装其他 Reader 提升性能。
  • 明确指定字符编码(如 StandardCharsets.UTF_8)避免乱码。
  • 使用 try-with-resources 自动关闭流,防止资源泄漏。

3. Writer

graph LR
    A[Writer] --> B[节点流]
    A --> C[处理流]

    B --> D[FileWriter<写入文件字符,默认编码>]
    B --> E[StringWriter<写入字符串字符>]
    B --> F[PipedWriter<用于线程间通信>]
    B --> G[CharArrayWriter<写入字符数组>]
    B --> H[InputStreamWriter<桥接流字节转字符,指定编码>]

    C --> I[BufferedWriter<缓冲提升性能+换行支持>]

1. FileWriter

场景:将字符数据写入文本文件(如日志、配置文件)。
技巧:默认使用系统字符编码(可能导致跨平台乱码),建议通过 OutputStreamWriter 指定编码(如 UTF-8),使用 BufferedWriter 包装提升性能。

1
2
3
4
5
try (FileWriter writer = new FileWriter("output.txt")) {
    writer.write("Hello FileWriter"); // 写入字符串
} catch (IOException e) {
    e.printStackTrace();
}

2. StringWriter

场景:在内存中动态构建字符串内容(如生成 JSON/XML 字符串)。
技巧:无需关闭流(close() 方法无实际作用),通过 toString()getBuffer() 获取结果。

1
2
3
4
StringWriter writer = new StringWriter();
writer.write("Hello ");
writer.append("StringWriter");
String result = writer.toString(); // 输出: Hello StringWriter

3. PipedWriter

场景:与 PipedReader 配合实现线程间字符数据通信。
技巧:必须调用 connect() 连接管道,确保线程安全(写入和读取线程需同步)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
writer.connect(reader);

// 生产者线程
new Thread(() -> {
    try (writer) {
        writer.write("Data from PipedWriter");
    } catch (IOException e) { e.printStackTrace(); }
}).start();

// 消费者线程
new Thread(() -> {
    try (reader) {
        int charData;
        while ((charData = reader.read()) != -1) {
            System.out.print((char) charData); // 输出: Data from PipedWriter
        }
    } catch (IOException e) { e.printStackTrace(); }
}).start();

4. CharArrayWriter

场景:向内存中的字符数组写入数据(如动态构建文本内容)。
技巧:可重复访问数据(通过 toCharArray()toString()),自动扩展缓冲区,无需预分配大小。

1
2
3
4
CharArrayWriter writer = new CharArrayWriter();
writer.write("Hello ");
writer.write("CharArrayWriter");
char[] data = writer.toCharArray(); // 获取字符数组

5. OutputStreamWriter

场景:将字符流转换为字节流并写入文件或网络(指定编码)。
技巧:必须包装一个 OutputStream(如 FileOutputStream),优先使用 StandardCharsets.UTF_8 避免编码问题。

1
2
3
4
5
6
try (OutputStream os = new FileOutputStream("data.txt");
    Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
    writer.write("UTF-8 编码文本");
} catch (IOException e) {
    e.printStackTrace();
}

总结对比表

核心场景 关键技巧
FileWriter 写入文本文件 配合 BufferedWriter 提升性能
StringWriter 动态构建字符串 通过 toString() 获取结果
PipedWriter 线程间字符通信 必须连接 PipedReader
CharArrayWriter 内存字符数组操作 可重复访问数据
OutputStreamWriter 字符转字节流(指定编码) 指定编码避免乱码

最佳实践

  1. 优先使用缓冲流:用 BufferedWriter 包装 FileWriterOutputStreamWriter 提升性能。

    1
    2
    3
    
    try (BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt"))) {
        writer.write("Buffered content");
    }
    
  2. 明确指定编码:避免依赖系统默认编码,防止跨平台问题。

  3. 线程安全处理PipedWriter 需同步生产者和消费者线程。

4. 示例场景图示

1
2
3
4
5
读取文本文件(UTF-8 编码):
FileInputStream (字节流) → InputStreamReader (转字符流) → BufferedReader (缓冲按行读取)

写入二进制数据:
DataOutputStream (装饰器) → BufferedOutputStream (缓冲) → FileOutputStream (目标文件)

总结

Java 核心工具涵盖了数据处理、并发、模块化等关键领域。通过合理使用这些工具,可以显著提升代码质量和开发效率。