1. 引言
本文将深入对比Java中的PrintStream和PrintWriter两个类,帮助开发者根据实际场景选择最合适的实现。在开始前,建议先了解这两个类的基础用法(可参考相关文档)。
2. PrintStream与PrintWriter的共性
这两个类功能高度重叠,容易让开发者混淆。我们先看它们的共同点,再分析差异。
2.1. 字符编码支持
字符编码确保文本在不同平台间保持一致解析。自JDK 1.4起,PrintStream的构造器新增了编码参数:
public PrintStream(OutputStream out, boolean autoFlush, String encoding) throws UnsupportedEncodingException {
this(requireNonNull(out, "Null output stream"), autoFlush, toCharset(encoding));
}
而PrintWriter从诞生之初就支持编码参数:
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) {
this(new BufferedWriter(new OutputStreamWriter(out, charset)), autoFlush);
// save print stream for error propagation
if (out instanceof java.io.PrintStream) {
psOut = (PrintStream) out;
}
}
⚠️ 关键点:若未指定编码,两者都会使用平台默认编码。
2.2. 文件写入能力
两者都支持直接写入文件,可通过构造器传入文件路径或File对象:
PrintStream示例:
public PrintStream(File file) throws FileNotFoundException {
this(false, new FileOutputStream(file));
}
PrintWriter示例:
public PrintWriter(File file) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))), false);
}
虽然都能写文件,但底层实现差异显著(后续章节会展开)。
3. PrintStream与PrintWriter的差异
尽管功能相似,但底层实现差异决定了它们适用场景不同。
3.1. 数据处理机制
PrintStream继承自OutputStream,属于字节流,逐字节处理数据。而PrintWriter是字符流,逐字符处理并自动进行Unicode转换。
3.2. 非文本数据处理
处理二进制文件时差异明显。以下代码对比写入PNG文件的效果:
public class DataStream {
public static void main (String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream("image.png");
PrintStream printStream = new PrintStream("ps.png");
int b;
while ((b = inputStream.read()) != -1) {
printStream.write(b);
}
printStream.close();
FileReader reader = new FileReader("image.png");
PrintWriter writer = new PrintWriter("pw.png");
int c;
while ((c = reader.read()) != -1) {
writer.write(c);
}
writer.close();
inputStream.close();
}
}
结果对比:
- ✅ ps.png:原始数据完整保留(字节流处理)
- ❌ pw.png:文件损坏(字符流错误解析二进制数据)
踩坑提示:用PrintWriter处理二进制文件会导致数据损坏!
3.3. 文本数据处理
OutputStream直接写字符串会报错:
public class PrintStreamWriter {
public static void main (String[] args) throws IOException {
OutputStream out = new FileOutputStream("TestFile.txt");
out.write("foobar"); // 编译失败!
out.flush();
out.close();
}
}
需转换为字节数组:
out.write("foobar".getBytes());
而PrintStream内部自动转换:
public class PrintStreamWriter {
public static void main (String[] args) throws IOException {
PrintStream out = new PrintStream("TestFile.txt");
out.print("Hello, world!"); // 内部调用getBytes()
out.flush();
out.close();
}
}
PrintWriter则直接处理字符:
public class PrintStreamWriter {
public static void main (String[] args) throws IOException {
PrintWriter out = new PrintWriter("TestFile.txt");
out.print("Hello, world!"); // 直接处理字符
out.flush();
out.close();
}
}
结论:文本I/O操作优先选择PrintWriter,其Unicode特性更利于国际化。
3.4. 刷新机制差异
自动刷新行为不同:
- PrintStream:写入字节数组、调用
println()
或换行符时触发 - PrintWriter:仅调用
println()
、printf()
或format()
时触发
测试代码:
public class AutoFlushExample {
public static void main (String[] args) throws IOException {
PrintStream printStream = new PrintStream(new FileOutputStream("autoFlushPrintStream.txt"), true);
printStream.write("Hello, world!\n".getBytes()); // 换行符触发刷新
printStream.close();
PrintWriter printWriter = new PrintWriter(new FileOutputStream("autoFlushPrintWriter.txt"), true);
printWriter.print("Hello, world!"); // 不保证刷新!
printWriter.close();
}
}
结果:
- ✅
autoFlushPrintStream.txt
:内容写入成功 - ❌
autoFlushPrintWriter.txt
:可能为空(未满足刷新条件)
简单粗暴解决方案:显式调用
flush()
确保数据写入!
4. 总结
通过对比分析,我们得出以下结论:
数据类型适配:
- 二进制数据 → 优先PrintStream(字节流)
- 文本数据 → 优先PrintWriter(字符流+Unicode)
国际化支持:
- PrintWriter的Unicode特性更利于多语言场景
刷新机制:
- PrintStream的自动刷新更可靠
- PrintWriter需显式调用
flush()
或使用特定方法
选择建议:
graph LR
A[数据类型] -->|二进制| B[PrintStream]
A -->|文本| C[PrintWriter]
C --> D[需要国际化?]
D -->|是| C
D -->|否| E[均可]
完整代码示例可在GitHub获取。