Java打印流案例:如何高效输出内容?从入门到实战
目录导读
-
打印流的核心概念与作用

-
标准打印流:System.out与System.err
-
自定义打印流:PrintStream与PrintWriter详解
-
实战案例:格式化输出、文件打印与重定向
-
常见问题与性能优化
-
问答环节
打印流的核心概念与作用
在Java I/O体系中,打印流(PrintStream与PrintWriter)是最常用的输出工具之一,它们本质上是装饰流,封装了底层的字节或字符输出流,提供了便捷的打印方法(如print()、println()、printf()),无论你是输出到控制台、文件还是网络,打印流都能简化字符串、数字、对象等数据的输出过程。
核心优势:
- 自动刷新:支持自动刷新缓冲区(autoFlush)
- 异常处理:内部捕获IOException,避免因输出失败导致程序中断(但可通过
checkError()检查) - 格式化支持:通过
printf()实现类似C语言的格式化输出
小贴士:Java的
System.out本质就是一个PrintStream对象,默认连接到控制台。
标准打印流:System.out与System.err
1 System.out
最常用的打印流,用于输出正常信息,示例:
public class OutDemo {
public static void main(String[] args) {
System.out.println("Hello, World!");
System.out.print("不会换行");
System.out.printf("格式化:%s 年龄 %d", "张三", 25);
}
}
注意:System.out默认使用平台编码,中文可能乱码,可通过-Dfile.encoding=UTF-8解决。
2 System.err
用于输出错误信息,与System.out独立,默认输出到控制台但线程安全级别不同,示例如下:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.err.println("发生除零错误:" + e.getMessage());
}
实际开发建议:将程序标准输出与错误输出分开重定向到不同日志文件,更利于问题排查。
核心问答
问:System.out和System.err能重定向到文件吗?
答:可以,通过System.setOut(new PrintStream(new FileOutputStream("out.txt")))即可,注意要捕获FileNotFoundException。
自定义打印流:PrintStream与PrintWriter详解
1 PrintStream
处理字节流,支持自动刷新,常用于二进制数据场景,构造函数常用:
PrintStream(OutputStream out) // 默认无自动刷新 PrintStream(OutputStream out, boolean autoFlush) // 指定自动刷新 PrintStream(OutputStream out, boolean autoFlush, String encoding) // 指定编码
示例:输出到文件并自动刷新
try (PrintStream ps = new PrintStream(new FileOutputStream("log.txt"), true)) {
ps.println("这是一条日志");
ps.printf("当前时间:%tF", System.currentTimeMillis());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
2 PrintWriter
处理字符流,支持国际化字符编码,推荐用于文本输出,构造方法:
PrintWriter(Writer out) PrintWriter(OutputStream out, boolean autoFlush) PrintWriter(String fileName, String csn) // 直接指定文件名与编码
示例:带编码的文件输出
try (PrintWriter pw = new PrintWriter("output.txt", "UTF-8")) {
pw.println("中文内容");
pw.printf("数值:%.2f", 123.456);
} catch (IOException e) {
e.printStackTrace();
}
核心区别对比
| 特性 | PrintStream | PrintWriter |
|---|---|---|
| 处理数据 | 字节流 | 字符流 |
| 自动刷新 | 支持(写入字节数组或换行时) | 支持(调用println/printf时) |
| 编码支持 | 需手动指定 | 构造时直接指定 |
| 推荐场景 | 二进制混合文本 | 纯文本国际化 |
实战案例:格式化输出、文件打印与重定向
案例1:表格格式化输出
使用printf()实现对齐:
System.out.printf("%-10s %-8s %-10s%n", "姓名", "年龄", "城市");
System.out.printf("%-10s %-8d %-10s%n", "李四", 28, "北京");
System.out.printf("%-10s %-8d %-10s%n", "王五", 22, "上海");
输出效果:
姓名 年龄 城市
李四 28 北京
王五 22 上海
案例2:日志文件打印(带时间戳)
import java.io.*;
import java.util.Date;
public class LogDemo {
public static void main(String[] args) {
try (PrintWriter log = new PrintWriter(
new FileWriter("app.log", true), true)) {
log.printf("[%tF %tT] 用户登录:%s%n",
new Date(), new Date(), "admin");
log.println("操作:查询订单");
} catch (IOException e) {
System.err.println("日志写入失败: " + e.getMessage());
}
}
}
案例3:输出重定向与恢复
// 保存原始流
PrintStream originalOut = System.out;
try (PrintStream fileOut = new PrintStream(
new FileOutputStream("redirect.txt"))) {
System.setOut(fileOut);
System.out.println("这句话会写入文件,不会显示在控制台");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 恢复控制台输出
System.setOut(originalOut);
}
System.out.println("这句话又回到控制台了");
核心问答
问:PrintStream和PrintWriter的自动刷新机制有什么差异?
答:PrintStream在写入byte[]、调用println()或遇到换行符\n时触发刷新;PrintWriter在调用println()、printf()或format()时触发,两者都需要构造时设置autoFlush=true。
常见问题与性能优化
问题1:性能瓶颈
直接使用System.out.println会在每次输出时触发系统调用,高并发场景下极其低效,优化方案:
- 批量输出:先拼接字符串再一次性输出
- 使用BufferedWriter:内部维护缓冲区,减少I/O次数
StringBuilder sb = new StringBuilder(); for (int i=0; i<10000; i++) { sb.append("line ").append(i).append("\n"); } System.out.print(sb.toString()); // 一次性输出
问题2:中文乱码
通过指定编码解决:
PrintWriter pw = new PrintWriter(new OutputStreamWriter(
System.out, "UTF-8"), true);
pw.println("中文测试");
问题3:自动关闭资源
使用try-with-resources确保流被正确关闭,避免资源泄漏:
try (PrintStream ps = new PrintStream(new FileOutputStream("test.dat"))) {
// 使用ps
} // 自动关闭
性能对比测试
| 方法 | 10万行输出耗时 |
|---|---|
| 逐行System.out.println | 约8秒 |
| StringBuilder拼接后输出 | 约0.2秒 |
| BufferedWriter包装后输出 | 约0.15秒 |
问答环节
问:PrintStream和PrintWriter的对象是否线程安全?
答:PrintStream的方法(如println、print)是同步的(synchronized),所以线程安全。PrintWriter的方法不是同步的,但内部包装的字符流可能是同步的(如BufferedWriter),推荐多线程场景使用PrintStream或自行同步。
问:如何将打印流输出到网络Socket?
答:可以通过Socket的OutputStream构造打印流:
Socket socket = new Socket("example.com", 8080);
PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
out.println("HTTP/1.1 200 OK\r\n\r\nHello");
问:打印流的checkError()方法有什么用?
答:检查底层流是否发生错误,返回true表示有错误。
PrintStream ps = new PrintStream(System.out);
ps.println("测试");
if (ps.checkError()) {
System.err.println("输出过程中发生错误");
}
问:在Spring Boot项目中,如何配置日志使用打印流?
答:Spring Boot默认使用Logback或Log4j2,但可以通过System.setOut将System.out重定向到日志框架。
import ch.qos.logback.core.OutputStreamAppender; LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); OutputStreamAppender appender = new OutputStreamAppender(); // 配置appender并设置到System.out
更推荐直接使用日志框架的API,而非System.out。
问:打印流的append()方法和print()有何区别?
答:append(CharSequence)返回PrintStream自身,支持链式调用,且内部调用print(),区别不大,但append符合Appendable接口规范。
通过本文学习,你应该能够灵活运用Java打印流解决各种输出需求。简单输出用System.out,文件输出用PrintWriter(指定编码),性能优化用批量拼接,在实际开发中,建议将打印流封装为工具类,统一管理日志格式与输出目标,如有其他疑问,欢迎在评论区交流。