1. 概述
文件处理是我们经常遇到的基本操作之一。在将数据写入文件时,通常会使用FileWriter
类。这个类中,有两个重要方法——flush()
和close()
,它们在管理文件输出流时各司其职。
本教程将介绍FileWriter
的常用用法,并深入探讨flush()
和close()
方法的区别。
2. FileWriter
与try-with-resources
Java中的try-with-resources
语句是管理资源的强大工具,特别是在处理文件操作,如文件处理时。它会在代码块退出(正常或异常)时自动关闭声明中的资源。
然而,有些情况下,直接使用FileWriter
与try-with-resources
可能不理想或没必要。FileWriter
在有无try-with-resources
的情况下行为可能会有所不同。接下来,我们将深入了解这一点。
2.1. 使用FileWriter
与try-with-resources
当我们在try-with-resources
块中使用FileWriter
时,文件输出流对象会自动在退出try-with-resources
块时被刷新并关闭。
下面是一个单元测试示例:
@Test
void whenUsingFileWriterWithTryWithResources_thenAutoClosed(@TempDir Path tmpDir) throws IOException {
Path filePath = tmpDir.resolve("auto-close.txt");
File file = filePath.toFile();
try (FileWriter fw = new FileWriter(file)) {
fw.write("Catch Me If You Can");
}
List<String> lines = Files.readAllLines(filePath);
assertEquals(List.of("Catch Me If You Can"), lines);
}
由于测试需要读写文件,我们使用了JUnit5的临时目录扩展(@TempDir
)。这个扩展允许我们在测试中专注于核心逻辑,无需手动创建和管理测试用的临时目录和文件。
正如测试方法所示,我们在try-with-resources
块中写入字符串。然后,当我们使用[Files.readAllLines()](/reading-file-in-java#1-reading-a-small-file)
检查文件内容时,会得到预期的结果。
2.2. 不使用try-with-resources
的FileWriter
然而,如果不使用try-with-resources
,FileWriter
对象不会自动被刷新和关闭:
@Test
void whenUsingFileWriterWithoutFlush_thenDataWontBeWritten(@TempDir Path tmpDir) throws IOException {
Path filePath = tmpDir.resolve("noFlush.txt");
File file = filePath.toFile();
FileWriter fw = new FileWriter(file);
fw.write("Catch Me If You Can");
List<String> lines = Files.readAllLines(filePath);
assertEquals(0, lines.size());
fw.close(); //close the resource
}
如上述测试所示,尽管我们通过FileWriter.write()
向文件写入了一些文本,但文件仍然为空。
现在,让我们解决这个问题。
3. FileWriter.flush()
和FileWriter.close()
本节首先解决“文件仍然为空”的问题,然后讨论flush()
和close()
方法之间的差异。
3.1. 解决“文件仍为空”问题
首先,快速理解为什么调用FileWriter.write()
后文件仍为空。当我们调用FileWriter.write()
时,数据并不会立即写入磁盘上的文件,而是暂存在缓冲区中。因此,为了在文件中看到数据,我们需要将缓冲区的数据刷新到文件中。
一个简单的方法是调用flush()
方法:
@Test
void whenUsingFileWriterWithFlush_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
Path filePath = tmpDir.resolve("flush1.txt");
File file = filePath.toFile();
FileWriter fw = new FileWriter(file);
fw.write("Catch Me If You Can");
fw.flush();
List<String> lines = Files.readAllLines(filePath);
assertEquals(List.of("Catch Me If You Can"), lines);
fw.close(); //close the resource
}
如图所示,调用flush()
后,我们可以读取文件并获取预期的数据。
另一种方法是调用close()
方法,将缓冲区的数据转移到文件。这是因为close()
方法首先执行flush
,然后关闭文件流。
接下来,我们创建一个测试来验证这一点:
@Test
void whenUsingFileWriterWithClose_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
Path filePath = tmpDir.resolve("close1.txt");
File file = filePath.toFile();
FileWriter fw = new FileWriter(file);
fw.write("Catch Me If You Can");
fw.close();
List<String> lines = Files.readAllLines(filePath);
assertEquals(List.of("Catch Me If You Can"), lines);
}
这看起来与flush()
调用类似。但是,在处理文件输出流时,这两个方法有不同的作用。
3.2. flush()
和close()
方法的区别
flush()
方法主要用于强制立即写出缓冲区中的任何数据,而不会关闭FileWriter
,以便继续对文件进行写入或追加操作。相反,close()
方法则既执行刷新操作又释放关联的资源。
换句话说,调用flush()
确保缓冲区中的数据及时写入磁盘,即使不关闭流也能继续写入文件。而当调用close()
时,它会将现有缓冲区的数据写入文件并关闭它。因此,除非重新打开流,例如初始化一个新的FileWriter
对象,否则无法再向文件写入数据。
下面通过一些例子来理解:
@Test
void whenUsingFileWriterWithFlushMultiTimes_thenGetExpectedResult(@TempDir Path tmpDir) throws IOException {
List<String> lines = List.of("Catch Me If You Can", "A Man Called Otto", "Saving Private Ryan");
Path filePath = tmpDir.resolve("flush2.txt");
File file = filePath.toFile();
FileWriter fw = new FileWriter(file);
for (String line : lines) {
fw.write(line + System.lineSeparator());
fw.flush();
}
List<String> linesInFile = Files.readAllLines(filePath);
assertEquals(lines, linesInFile);
fw.close(); //close the resource
}
在这个例子中,我们三次调用write()
分别写入三行到文件。每次write()
调用后,我们都调用了flush()
。最后,我们可以从目标文件中读取出三行。
然而,如果在调用FileWriter.close()
后尝试写入数据,将会抛出IOException
,错误信息为“Stream closed”:
@Test
void whenUsingFileWriterWithCloseMultiTimes_thenGetIOExpectedException(@TempDir Path tmpDir) throws IOException {
List<String> lines = List.of("Catch Me If You Can", "A Man Called Otto", "Saving Private Ryan");
Path filePath = tmpDir.resolve("close2.txt");
File file = filePath.toFile();
FileWriter fw = new FileWriter(file);
//write and close
fw.write(lines.get(0) + System.lineSeparator());
fw.close();
//writing again throws IOException
Throwable throwable = assertThrows(IOException.class, () -> fw.write(lines.get(1)));
assertEquals("Stream closed", throwable.getMessage());
}
4. 总结
在这篇文章中,我们探讨了FileWriter
的常用用法,并讨论了flush()
和close()
方法的区别。如往常一样,所有示例的完整源代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-io-apis-2。