修改大文件内容

Modify content of large file

我已经从我的数据库中的 json 文件中提取了我的表,现在我想读取这个文件并删除它们上面的所有双引号,看起来很简单并尝试了数百种解决方案,有些将我带到了内存不足的问题。我正在处理超过 1Gb size.The 代码的文件,你会发现下面的代码有一个奇怪的行为,我不明白为什么它 return 空文件

  public void replaceDoubleQuotes(String fileName){
    log.debug(" start formatting " + fileName + " ...");
    File firstFile = new File ("C:/sqlite/db/tables/" + fileName);
    String oldContent = "";
    String newContent = "";
    BufferedReader reader = null;
    BufferedWriter writer = null;
    FileWriter writerFile = null;
    String stringQuotes = "\\\\\"";
    try {
        reader = new BufferedReader(new FileReader(firstFile));
        writerFile = new FileWriter("C:/sqlite/db/tables/" + fileName);
        writer = new BufferedWriter(writerFile);
        
    while   (( oldContent = reader.readLine()) != null ){
        newContent = oldContent.replaceAll(stringQuotes, "");
        writer.write(newContent);
        }
    
    writer.flush();
    writer.close();
    } catch (Exception e) {
        log.error(e);
    }
}

当我尝试使用FileWriter(path,true)写入文件末尾时,程序不会停止增加文件内存,直到硬盘已满,感谢您的帮助

ps :我也尝试使用 subString 并附加新内容,然后我写了 subString 但也不起作用

TL;博士;

不要同时读写同一个文件。

问题

您的代码开始读取,然后立即截断它正在读取的文件。

 reader = new BufferedReader(new FileReader(firstFile));
 writerFile = new FileWriter("C:/sqlite/db/tables/" + fileName);
 writer = new BufferedWriter(writerFile);
    

第一行打开文件的读句柄。 第二行打开同一个文件的写句柄。 如果你看FileWriter构造函数的文档不是很清楚,但是当你不使用允许你指定append参数的构造函数时,默认值为false,意思是,如果文件已经存在,您将立即截断该文件。

此时(第 2 行)您刚刚删除了您要阅读的文件。所以你最终得到一个空文件。

使用 append=true 怎么样

嗯,那么文件在创建的时候没有被擦除,就“好”了。所以你的程序开始读取第一行,并输出(到同一个文件)过滤后的版本。

因此每次读取一行时,都会附加另一行。

难怪您的程序永远不会到达文件末尾:每次前进一行时,都会创建另一行进行处理。一般来说,您永远不会到达文件末尾(当然,如果文件是单行开头,您可能会到达,但这是一个极端情况)。

解决方案

写入一个临时文件,如果(且仅当)您成功了,然后在确实需要时交换文件。

此解决方案的优势:如果由于某种原因您的进程崩溃,您将保持原始文件不变,您可以稍后重试,这通常是一件好事。您的过程是“可重复的”。

缺点:在某些时候您需要 space 的两倍。 (虽然你可以压缩临时文件并减少这个因素但仍然)。

关于内存不足问题

处理任意大的文件时,您选择的路径(使用缓冲的 reader 和编写器)是正确的,因为您一次只使用一个 line-worth 内存。

因此它通常可以避免内存使用问题(当然,除非你有一个没有换行符的文件,在这种情况下它根本没有区别)。

其他解决方案,包括一次读取整个文件,然后在内存中执行 search/replace,然后将内容写回,并不能很好地缩放,所以最好避免这种计算。

不相关但重要

检查 try with resources 语法以正确关闭您的资源(reader / 作者)。在这里你忘了关闭 reader,而且你也没有适当地关闭 writer(即:在 finally 子句中)。

另一件事:我敢肯定,凡人编写的 java 程序都无法击败像 sedawk 这样的大多数 unix 平台(以及一些更多的)。也许您想检查自己在 java 中滚动是否值得 shell one-liner.

@GPI 已经提供了一个很好的答案,说明为什么同时读取和写入会导致您遇到的问题。同样值得注意的是,如果没有分配足够的堆,一次将 1gb 的数据读入堆肯定会导致 OutOfMemoryError,这很可能。要解决这个问题,您可以使用 InputStream 并一次读取文件块,然后写入另一个文件直到该过程完成,最后用修改后的文件替换现有文件并删除。使用这种方法,您甚至可以使用 ForkJoinTask 来帮助解决这个问题,因为这是一项庞大的工作。

旁注; 可能有比 create new file, write to new file, replace existing, delete new file.

更好的解决方案