Java 只打开 BufferedWriter 一次但多次重写内容

标签 java performance file-io

我正在运行一个长时间运行的操作,比如 10 万个作业。我想在每完成 100 个这样的作业后在文件中更新它的进度。

我正在使用 bufferedWriter 打开文件,附加模式为 false。写入它然后关闭它。每 100 个工作完成一次。所以文件打开和关闭会发生 1000 次。我可以通过只打开和关闭文件一次来进一步优化它吗?

    public static void writeMetaData(String writeDir, JSONObject jsonObject) throws Exception {
        String filePath = writeDir.concat("/").concat("metadata.txt");
        BufferedWriter metaDataWriter = Files.newBufferedWriter(Paths.get(filePath), StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING);
        metaDataWriter.write(jsonObject.toString());
        IOUtils.closeQuietly(metaDataWriter);
    }

for(int i =0 ; i < 100000; i++) {
    // do Something; 
    if(i % 100 == 0) {
        writeMetaData(writeDir, jsonObject); 
    }
}

文件应该只有一行。

100 个作业后的预期文件内容: 进度:100 200 个作业后的预期文件内容: 进度:200

这可以进一步优化吗?

最佳答案

首先,writeDir.concat("/").concat("metadata.txt") 之类的表达式会降低可读性 性能。直接的 writeDir + "/"+ "metadata.txt" 将提供更好的性能。但是由于你只是为了构造一个 Path 而构造一个字符串,所以更直接的做法是不要在你的代码中完成 Path 的工作,而是使用 Paths.get(writeDir, "metadata.txt").

您不能倒回 BufferedWriter,但可以倒回 FileChannel。因此,要保持 channel 打开并在需要时倒带,您必须在倒带后构造一个新的编写器:

public static void writeMetaData(FileChannel ch, JSONObject jsonObj) throws IOException {
    ch.position(0);
    if(ch.size() > 0) ch.truncate(0);

    Writer w = Channels.newWriter(ch, StandardCharsets.UTF_8.newEncoder(), 8192);
    w.write(jsonObj.toString());
    w.flush();
}
try(FileChannel ch = FileChannel.open(Paths.get(writeDir, "metadata.txt"),
        StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

    for(int i = 0; i < 100000; i++) {
        // do Something;
        if(i % 100 == 0) {
            writeMetaData(ch, jsonObject);
        }
    }
}

重要的是 Writer 的使用以 flush() 结束,以强制写入所有缓冲数据,而不是 close() 因为这也会关闭底层 channel 。请注意,此代码不会将编写器包装到 BufferedWriter 中;将文本编码为 UTF-8 已经是一种缓冲操作,通过为编码器请求更大的缓冲区,匹配 BufferedWriter 的默认缓冲区大小,我们可以获得相同的缓冲效果,而无需复制开销。

由于写作本身并不是目的,因此还有一个关于阅读方面的问题。如果读取器尝试在某些时间间隔读取数据,则存在与写入重叠的风险,从而获得不完整的数据。

你可以使用

public static void writeMetaData(FileChannel ch, JSONObject jsonObj) throws IOException {
    try(FileLock lock = ch.lock()) {
        ch.position(0);
        if(ch.size() > 0) ch.truncate(0);

        Writer w = Channels.newWriter(ch, StandardCharsets.UTF_8.newEncoder(), 8192);
        w.write(jsonObj.toString());
        w.flush();
    }
}

在写入过程中锁定文件。但根据系统的不同,文件锁定可能不是强制性的,但只会影响同样试图获得读取锁定的读者。


当您使用 JDK 11 或更新版本时,您可以考虑使用

for(int i = 0; i < 100000; i++) {
    // do Something;
    if(i % 100 == 0) {
        Files.writeString(Paths.get(writeDir, "metadata.txt"), jsonObject.toString());
    }
}

这显然以简单性取胜(是的,这是完整的代码,不需要额外的方法)。默认选项已经包含所需的 StandardCharsets.UTF_8StandardOpenOption.TRUNCATE_EXISTING

虽然它确实在内部打开和关闭文件,但它有一些其他的性能调整可以弥补。特别是在字符串仅由 ASCII 字符组成的可能情况下,因为实现会简单地将字符串的内部数组直接写入文件。

关于Java 只打开 BufferedWriter 一次但多次重写内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71386037/

相关文章:

java - 将 PDF 文件转换为字节数组的方法..(使用 outputstream() 进行刷新)

java - 不使用扫描仪读取文件

Scala 中 Set union 的性能问题

Android ORMlite 与 queryForAll() 的性能问题

java - Selenium 代码查找段落中的字数

java - 将对象添加到数组列表

html - 在大型 HTML 表格中,输入之间的切换很慢

SVN命令删除所有本地丢失的文件

java - 如何运行 parent 方法两次

java - 上传文档并显示文档状态 - Selenium WebDriver - Java