PrintStream 的 println 不格式化,仅拼接字符串并换行;需格式化时用 printf/format;写文件须用 FileOutputStream 包装并指定 UTF-8 编码、追加模式和自动刷新;生产日志应选 slf4j 等专业框架。

PrintStream 的 println 本身不格式化,只是拼接后换行
println 方法不会解析占位符或做类型格式化——它只把参数调用 String.valueOf() 转成字符串,再追加平台换行符(\r\n 或 \n)。所谓“方便”,其实是靠 Java 自动装箱 + toString() 隐式转换实现的,比如 println(123)、println(new Date()) 都能直接输出,但你无法控制小数位数、日期格式、对齐方式等。
真正需要格式化(如 "用户ID: %06d, 时间: %tF %tT")时,必须用 printf() 或 format(),它们才接受格式字符串和参数列表。
-
println("user=" + uid + ", time=" + new Date())—— 简单但易出错,字符串拼接无类型安全,性能差 -
printf("user=%06d, time=%tF %tT%n", uid, now, now)—— 精确控制,但需注意%n是平台无关换行,%n不等于\n - 别混用:
print("msg"); printf("data")不会自动换行,容易漏flush导致日志滞留在缓冲区
写文件时必须用 FileOutputStream 包装,不能直接传路径
PrintStream 构造器**不接受 String 路径或 File 对象**。常见错误是写成 new PrintStream("app.log"),这会触发编译报错:找不到匹配构造方法。
正确链路是:先创建 FileOutputStream,再把它传进去。尤其要注意两个关键参数:
- 追加模式:用
new FileOutputStream("app.log", true),否则每次新建会清空原文件 - 编码指定:默认用
System.getProperty("file.encoding")(Windows 常为 GBK),中文必乱码;必须显式传入StandardCharsets.UTF_8 - 自动刷新:第二个布尔参数设为
true,仅对println、printf、format生效,print()不触发
完整写法示例:
PrintStream log = new PrintStream(new FileOutputStream("app.log", true), true, StandardCharsets.UTF_8);
System.setOut() 改变全局输出,但有隐藏风险
想让所有 System.out.println() 日志自动落盘?可以用 System.setOut(log)。这确实省事,但要注意:
- 它修改的是 JVM 全局
out流,其他模块(如第三方库、测试框架)的System.out输出也会被重定向,可能干扰诊断 - 一旦设置,除非手动恢复(
System.setOut(originalOut)),否则无法局部关闭 -
System.out默认不close(),但你包装的PrintStream必须在程序退出前close(),否则缓冲区数据丢失 - 多线程下若共享一个
PrintStream实例,println是线程安全的(内部同步),但性能会下降
比 PrintStream 更适合日志的替代方案
如果你真在做日志(而非临时调试输出),PrintStream 缺少关键能力:无日志级别、无时间戳自动注入、无滚动策略、无异常堆栈完整捕获。实际项目中更稳妥的选择是:
-
PrintWriter:字符流,天然支持Charset,构造更简洁(Files.newBufferedWriter(Paths.get("log.txt"), UTF_8)) -
try-with-resources:避免忘记close(),例如try (PrintWriter pw = Files.newBufferedWriter(Paths.get("log.txt"), UTF_8)) { pw.printf("[%s] %s%n", Instant.now(), msg); } - 真正生产级日志应跳过手写流,直接用
slf4j + logback或java.util.logging,它们内置异步、过滤、归档
用 PrintStream 写日志最易被忽略的点:你以为 println 会立刻写入磁盘,其实它只写进内存缓冲区;没关流、没 flush、没开 auto-flush,重启后日志就没了。


















