背景
本文是《Java 后端从小白到大神》修仙系列第九篇
,正式进入Java后端
世界,本篇文章主要聊Java基础
。若想详细学习请点击首篇博文,我们开始把。
文章概览
- 核心工具类(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
}
}
|
场景:字节流通向字符流的桥梁,可指定字符编码(如 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 |
字符转字节流(指定编码) |
指定编码避免乱码 |
最佳实践:
-
优先使用缓冲流:用 BufferedWriter
包装 FileWriter
或 OutputStreamWriter
提升性能。
1
2
3
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt"))) {
writer.write("Buffered content");
}
|
-
明确指定编码:避免依赖系统默认编码,防止跨平台问题。
-
线程安全处理:PipedWriter
需同步生产者和消费者线程。
4. 示例场景图示
1
2
3
4
5
|
读取文本文件(UTF-8 编码):
FileInputStream (字节流) → InputStreamReader (转字符流) → BufferedReader (缓冲按行读取)
写入二进制数据:
DataOutputStream (装饰器) → BufferedOutputStream (缓冲) → FileOutputStream (目标文件)
|
总结
Java 核心工具涵盖了数据处理、并发、模块化等关键领域。通过合理使用这些工具,可以显著提升代码质量和开发效率。