1. 概述
在调试或向用户显示信息时,将输出打印到控制台是常见的操作。然而,有时可能需要将控制台输出保存到文本文件中,以便进行进一步分析或文档记录。
在这篇教程中,我们将探讨如何在Java中将控制台输出重定向到文本文件。
2. 准备工作
当我们谈论将控制台输出写入文本文件时,可能会遇到两种情况:
- 只有文件 - 将所有输出重定向到文件,不将任何内容打印到控制台。
- 控制台和文件 - 输出写入控制台和文件。
本教程将涵盖这两种情况。
在我们进入编码部分之前,先准备一些要写入控制台的文本。为了便于测试,我们在一个字符串列表中放入三条行:
final static List<String> OUTPUT_LINES = Lists.newArrayList(
"I came",
"I saw",
"I conquered");
接下来,我们将讨论如何将输出从控制台重定向到文件。因此,我们将使用单元测试断言来验证文件是否包含预期的内容。
JUnit 5的临时目录功能允许我们创建文件,将数据写入文件,并在验证后自动删除文件。因此,我们将在测试中使用它。
现在,让我们看看如何实现重定向。
3. 只将输出写入文件
通常,我们使用System.out.println()
来打印文本到控制台。System.out
是默认的PrintStream
对象。System
类提供了setOut()
方法,允许我们用我们的PrintStream
对象替换默认的“out”。
由于我们要将数据写入文件,我们可以创建一个PrintStream
,来自FileOutputStream
。
接下来,我们创建一个测试方法来检查这个想法是否可行:
@Test
void whenReplacingSystemOutPrintStreamWithFileOutputStream_thenOutputsGoToFile(@TempDir Path tempDir) throws IOException {
PrintStream originalOut = System.out;
Path outputFilePath = tempDir.resolve("file-output.txt");
PrintStream out = new PrintStream(Files.newOutputStream(outputFilePath), true);
System.setOut(out);
OUTPUT_LINES.forEach(line -> System.out.println(line));
assertTrue(outputFilePath.toFile().exists(), "The file exists");
assertLinesMatch(OUTPUT_LINES, Files.readAllLines(outputFilePath));
System.setOut(originalOut);
}
在测试中,我们首先备份默认的System.out
。然后,我们构造一个PrintStream
对象out
,它包装了一个FileOutputStream
。接着,我们将默认的System.out
替换为我们的“out”。
这些操作使System.out.println()
将数据写入文件file-output.txt
,而不是控制台。我们使用assertLinesMatch()
方法验证了文件内容。
最后,我们将System.out
恢复为默认值。
如果运行测试,它会通过。此外,不会有任何输出打印到控制台。
4. 创建DualPrintStream
类
现在,让我们看看如何将数据打印到控制台和文件。换句话说,我们需要两个PrintStream
对象。
由于System.setOut()
只接受一个PrintStream
参数,我们不能传递两个PrintStream
。但是,我们可以创建一个新的PrintStream
子类来携带额外的一个PrintStream
对象:
class DualPrintStream extends PrintStream {
private final PrintStream second;
public DualPrintStream(OutputStream main, PrintStream second) {
super(main);
this.second = second;
}
...
}
DualPrintStream
扩展了PrintStream
。此外,我们可以在构造函数中传入一个额外的PrintStream
对象(第二个)。然后,我们需要覆盖PrintStream
的write()
方法,以便第二个PrintStream
可以搭便车并执行相同的操作:
class DualPrintStream extends PrintStream {
...
@Override
public void write(byte[] b) throws IOException {
super.write(b);
second.write(b);
}
}
现在,让我们检查它是否按预期工作:
@Test
void whenUsingDualPrintStream_thenOutputsGoToConsoleAndFile(@TempDir Path tempDir) throws IOException {
PrintStream originalOut = System.out;
Path outputFilePath = tempDir.resolve("dual-output.txt");
DualPrintStream dualOut = new DualPrintStream(Files.newOutputStream(outputFilePath), System.out);
System.setOut(dualOut);
OUTPUT_LINES.forEach(line -> System.out.println(line));
assertTrue(outputFilePath.toFile().exists(), "The file exists");
assertLinesMatch(OUTPUT_LINES, Files.readAllLines(outputFilePath));
System.setOut(originalOut);
}
当运行时,它通过了,这意味着文本已被写入文件。同时,我们也能在控制台上看到这三条行。
最后,值得注意的是,PrintStream
还有其他方法,我们需要覆盖它们以保持文件和控制台流同步,如close()
, flush()
,以及write()
的各种变体。我们还应该覆盖checkError()
方法以优雅地处理IOExceptions
。
5. 总结
在这篇文章中,我们学习了如何通过替换默认的System.out
使System.out.println()
将数据写入文件。
如往常一样,这里展示的所有代码片段可在GitHub上找到。