1. 引言
在 Java 日常开发中,PrintStream 是最常用的输出流之一。无论是控制台打印、日志输出,还是文件写入,都能看到它的身影。本文将从基础概念出发,结合代码示例,系统讲解 PrintStream 的核心用法、常见陷阱和实战技巧。
2. 什么是 PrintStream
PrintStream 是 java.io 包中的一个字节输出流类,它继承自 FilterOutputStream。它的核心特点是:
- 自动刷新:部分构造方法支持自动刷新缓冲区。
- 便捷打印方法 :提供
print()和println()系列方法,支持各种基本数据类型和对象。 - 不抛受检异常 :写入方法不会抛出
IOException,而是通过内部状态记录错误。
最典型的实例就是 System.out,它就是一个 PrintStream 对象,默认输出到控制台。
3. 创建 PrintStream
可以通过多种方式创建 PrintStream 实例,常见的有以下几种:
java
import java.io.FileOutputStream;
import java.io.PrintStream;
public class CreatePrintStream {
public static void main(String[] args) throws Exception {
// 方式一:包装已有输出流
PrintStream ps1 = new PrintStream(System.out);
// 方式二:指定文件名,自动创建文件
PrintStream ps2 = new PrintStream("output.txt");
// 方式三:指定文件输出流,并开启自动刷新
PrintStream ps3 = new PrintStream(new FileOutputStream("log.txt"), true);
// 方式四:指定字符集
PrintStream ps4 = new PrintStream("data.txt", "UTF-8");
ps1.println("Hello PrintStream");
ps1.close();
}
}
其中,第二个参数 autoFlush 为 true 时,遇到换行符或字节数组写入时会自动刷新缓冲区。
4. 常用方法详解
PrintStream 提供了丰富的打印方法,下面按类别介绍最常用的部分。
4.1 print 与 println
print() 输出内容但不换行,println() 输出内容后自动换行。两者都支持多种重载形式:
java
public class PrintMethods {
public static void main(String[] args) {
int num = 42;
double pi = 3.14159;
String name = "Java";
Object obj = new Object();
System.out.print("数字: ");
System.out.println(num);
System.out.println("浮点数: " + pi);
System.out.println("字符串: " + name);
System.out.println("对象: " + obj);
}
}
4.2 printf 格式化输出
printf() 支持类似 C 语言的格式化输出,是构建整齐文本的利器:
java
public class PrintfDemo {
public static void main(String[] args) {
String product = "笔记本电脑";
double price = 5999.5;
int stock = 120;
System.out.printf("商品:%s,价格:%.2f 元,库存:%d 件%n", product, price, stock);
System.out.printf("百分比:%.1f%%%n", 87.35);
}
}
常用占位符包括 %s(字符串)、%d(整数)、%f(浮点数)、%n(换行符)。
4.3 write 方法
作为输出流,PrintStream 也支持底层字节写入:
java
public class WriteDemo {
public static void main(String[] args) {
PrintStream ps = new PrintStream(System.out);
byte[] data = {65, 66, 67, 10}; // A B C 换行
ps.write(data, 0, data.length);
ps.flush();
ps.close();
}
}
5. 重定向 System.out
通过 System.setOut() 可以将标准输出重定向到文件或其他流,这在日志记录和测试场景中非常实用:
java
import java.io.FileOutputStream;
import java.io.PrintStream;
public class RedirectOut {
public static void main(String[] args) throws Exception {
PrintStream fileOut = new PrintStream(new FileOutputStream("app.log"), true);
System.setOut(fileOut);
System.out.println("这条日志会写入文件");
System.out.println("而不是显示在控制台");
// 恢复标准输出
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
System.out.println("控制台输出恢复");
}
}
6. 常见陷阱与注意事项
使用 PrintStream 时,有几个容易踩坑的地方需要特别留意。
6.1 异常被吞掉
PrintStream 的写入方法不抛出 IOException,错误会被记录在内部状态中。需要通过 checkError() 主动检查:
java
public class CheckErrorDemo {
public static void main(String[] args) {
PrintStream ps = new PrintStream(System.out);
ps.println("写入内容");
if (ps.checkError()) {
System.err.println("输出过程中发生错误");
}
ps.close();
}
}
6.2 字符集问题
默认构造方法使用平台默认字符集,在跨平台场景下可能导致乱码。建议显式指定字符集:
java
PrintStream ps = new PrintStream("output.txt", StandardCharsets.UTF_8);
6.3 资源关闭
包装了其他流的 PrintStream 在关闭时,会同时关闭底层流。如果不想关闭底层流,可以使用 System.out 或自行管理生命周期。
7. 实战:日志记录工具
下面结合前面的知识,实现一个简单的日志工具类:
java
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class SimpleLogger {
private final PrintStream out;
public SimpleLogger(String filePath) throws Exception {
this.out = new PrintStream(new FileOutputStream(filePath, true), true, "UTF-8");
}
public void info(String message) {
log("INFO", message);
}
public void error(String message) {
log("ERROR", message);
}
private void log(String level, String message) {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
out.printf("[%s] [%s] %s%n", time, level, message);
}
public void close() {
out.close();
}
public static void main(String[] args) throws Exception {
SimpleLogger logger = new SimpleLogger("app.log");
logger.info("应用启动");
logger.error("数据库连接失败");
logger.close();
}
}
8. 总结
PrintStream 凭借便捷的打印方法和灵活的重定向能力,在 Java 输出场景中占据重要地位。使用时需要注意异常检查、字符集指定和资源管理三个关键点。掌握这些内容,就能在日常开发中更加得心应手地处理输出需求。