【问题标题】:Save huge amount of Strings in file during program execution in Java在 Java 程序执行期间在文件中保存大量字符串
【发布时间】:2016-12-26 20:39:08
【问题描述】:

在我的程序中,主要逻辑是用不同的方法构造字符串,然后以特定的顺序将它们保存在文件中。但是我的内存消耗非常高,所以想知道如何在内存中保存更少的字符串。所以我会尽量简化程序以方便阅读。我的小字符串生成方法确实喜欢:

那么我的主要逻辑是这样的:

        BufferedWriter bw = new BufferedWriter(fw);
        for(int i=1;i<1_000_000_000;i++){
            bw.write(processMethod(i));
        }
        bw.close();

其中processMethod 是一个调用generateTag 等方法m 次的方法,使用StringBuilder 生成String,然后保存到BufferedWriter

public static String generateTag(final String tagName, final String name, final String value) {
        StringBuilder attribute = new StringBuilder();
        attribute.append("<tag:");
        attribute.append(tagName);
        attribute.append(" name=\"");
        attribute.append(name);
        attribute.append("\">");
        attribute.append(value);
        attribute.append("</tag:");
        attribute.append(tagName);
        attribute.append(">");
        return attribute.toString();
    }

所以当我启动 processMethod 时执行 1_000_000_000 次,然后 m 次被称为 generateTag 之类的方法。我在内存中有 1_000_000_000 * m 个字符串。我怎样才能轻松删除他们的创作?我认为是这样的:

public static String generateTag(final String tagName, final String name, final String value, final BufferedWriter bf) {
   ....
   bf.write(someBuilder.toString());
   ..

}

但我认为传递BufferedWriter 并不是一件好事。 你能建议我少一些 String 创建的描述吗?

【问题讨论】:

  • 为什么不直接写入文件而不是将字符串保存在内存中?也就是说,为什么你认为传递BufferedWriter 不好?
  • 但是如何在每个方法中打开新的 BufferWrite 到文件并只传递文件的绝对路径?
  • 不,您只通过processMethod 将主BufferedWriter 从主传递。只有一个BufferedWriter,否则他们不会按顺序写入同一个文件,对吧?
  • 不是一个真正的解决方案,但您应该考虑使用启用了字符串重复数据删除的 G1GC 垃圾收集器。这可以为您节省大量内存
  • 你必须传递一些东西,这样每次对 generateTag 的调用不会最终产生一个字符串。 BufferedWriter、Writer 或 StringBuilder 或 ByteBuffer - 你的电话。

标签: java string memory memory-management


【解决方案1】:

如果您的程序确实只是一个接一个地调用方法并且这些方法生成字符串并且这些字符串按照它们生成的顺序写入文件,那么使用主BufferedWriter直接写入文件会更简单:

try (BufferedWriter bw = new BufferedWriter(fw);) {
    for(int i=1;i<1_000_000_000;i++){
        processMethod(i, bw);
    }
}

(请注意,我使用 try-with-resources 来自动关闭缓冲写入器)。

然后当processMethod 调用generateTag 时,它会将缓冲的写入器传递给它:

public void processMethod(int i, BufferedWriter bw) {
    ...
    generateTag(...,bw);
    ...
}

generateTag 将是:

public static void generateTag(final String tagName, final String name, final String value, final BufferedWriter bw) {
        bw.write("<tag:");
        bw.write(tagName);
        bw.write(" name=\"");
        bw.write(name);
        bw.write("\">");
        bw.write(value);
        bw.write("</tag:");
        bw.write(tagName);
        bw.write(">");
    }

由于BufferedWriter 被缓冲,这意味着不是每次调用write 时都有磁盘访问,而是每次缓冲区被填满。因此,这不会影响您的磁盘访问速度。但它会为你节省所有的记忆。

当然,如果你不串行写结果,或者一个方法的结果依赖于另一个方法的结果,那么你需要修改这个,但是,你应该一有下一个就写一块String 准备好了。

【讨论】:

    【解决方案2】:

    您可能会考虑的一件事是不要将它们保存在内存中,而是尽快将它们写入...

    jdom、dom 和 dom4j 等许多 XML api 助长了在内存中构建树模型的有害习惯,而实际上,尽快将字节转储到输出缓冲区中效率更高...

    您可以重写您的方法以包含一个输出流变量,并用输出流清除所有字节。

    【讨论】:

    • 是的,这样会更好。谢谢。
    【解决方案3】:

    使用Appendable 抽象来确保generateTag(以及processMethod,如果您愿意)不依赖于BufferedWriter。事实上,我们甚至可以传递一个StringBuilder 来做和以前一样的功能,我们可以使用它来提供适当的重载:

    public static CharSequence generateTag(
        final String tagName, final String name, final String value) {
    
        StringBuilder attribute=new StringBuilder(80);
        try { generateTag(tagName, name, value, attribute); }
        catch (IOException ex) { throw new AssertionError(ex); }
        return attribute;
    }
    public static void generateTag(
        final String tagName, final String name, final String value, Appendable attribute)
        throws IOException {
    
        attribute.append("<tag:");
        attribute.append(tagName);
        attribute.append(" name=\"");
        attribute.append(name);
        attribute.append("\">");
        attribute.append(value);
        attribute.append("</tag:");
        attribute.append(tagName);
        attribute.append(">");
    }
    

    请注意,重载返回 CharSequence 而不是 String 以便能够省略最后的 StringBuilder.toString() 复制步骤。不幸的是,考虑到current implementation,用它在BufferedWriter 上调用append(CharSequence) 不会得到回报。还有APIs allowing to use CharSequence directly

    【讨论】:

    • 所以如果我这样做,我可以从实现 Appendable 的类型传递变量:BufferedWriter、CharArrayWriter、CharBuffer、FileWriter、FilterWriter、LogStream、OutputStreamWriter、PipedWriter、PrintStream、PrintWriter、StringBuffer、StringBuilder、StringWriter , 作家。但是如果在运行时有 BufferedWriter 那么 toString() 会在追加时被调用吗?其他 API 使用非缓冲版本,这可能是个问题。
    • 不,不,如果您使用generateTag(tagName,name,value,bufferedWriter),则不会创建临时字符串。仅当您使用bufferedWriter.append(generateTag(tagName,name,value)) 时,CharSequence 才会转换为String。对于您的主要用例,您可以简单地使用第一个变体。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 2012-04-20
    相关资源
    最近更新 更多