分配大的新String时如何使用更少的内存

时间:2013-10-20 16:16:05

标签: java string memory

Java String是不可变的,所以

当你创建一个字符串时,会在堆中为它分配一块内存,当你更改它的值时,会为该字符串创建一个新的内存块,而旧的内存块就有资格进行垃圾收集,示例

String str = func1_return_big_string_1()"; //not literal
String str= func2_return_big_string_2()"; //not literal

但是由于垃圾收集需要时间才能启动,因此我们实际上在堆中包含大字符串1和1的内存。如果发生这种情况,它们对我来说可能是一个问题。

有没有办法让大字符串2在字符串1的内存中使用相同的位置,所以当我们将大字符串2分配给str时,我们不需要额外的空间。

编辑: 感谢所有的输入,最后我意识到我不应该期望java代码表现得像c ++代码(即不同的内存占用)。我写了一个c ++ 11演示,它按预期工作,最大内存占用大约20M(我试图加载的最大文件),右值引用和移动赋值运算符都按预期启动。 下面的演示在VS2012中用c ++ 11完成。

#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <thread>
using namespace std;

string readFile(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}

class test{
public:
    string m_content;
};

int _tmain(int argc, _TCHAR* argv[])
{
    string base("c:\\data");
    string ext(".bin");
    string filename;
    test t;
    //std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    cout << "about to start" << endl;
    for(int i=0; i<=50; ++i) {
        cout << i << endl;
        filename = base + std::to_string(i) + ext;
        //rvalue reference & move assignment operator here
        //so no unnecessary copy at all
        t.m_content = readFile(filename);
        cout << "szie of content" << t.m_content.length() << endl;
    }
    cout << "end" << endl;
    system("pause");
    return 0;
}

5 个答案:

答案 0 :(得分:2)

使用StringBuffer,StringBuffer.append()

答案 1 :(得分:2)

我看到了几个选项:

  1. 使用char[]
  2. 使用公共可重用缓冲区将StringBuilder复制到您的版本MyStringBuilder中。它的主要缺点是缺乏正则表达式。这就是我在提高性能时所做的事情。
  3. Hack for JDK&lt; = 6:有一个受保护的构造函数可以重用字符串/ wrap char缓冲区。它不再适用于JDK 7+。人们需要对此非常谨慎,一旦你拥有C / C ++背景,这不是问题。
  4. 使用公共可重用缓冲区将String复制到MutableString。我不认为添加自定义正则表达式匹配器会有问题,因为有很多可用的。

答案 2 :(得分:1)

非实习字符串应该不重要。如果开始内存不足,垃圾收集器将删除不再引用的任何对象。

Interned Strings更难收集,详见Garbage collection of String literals

编辑非实习字符串就像普通对象一样。一旦没有更多的引用,就会收集垃圾。

如果str是指向原始字符串的唯一引用,而str被更改为指向其他字符串,则原始字符串可用于垃圾回收。因此,您不再需要担心内存不足,因为如果需要内存,JVM将收集内存。

答案 3 :(得分:1)

为了避免在内存中同时使用旧的和新的String,您可以通过为变量分配null来明确允许GC清理它:

String str;
str = func1_return_big_string_1();
str = null; // Now, GC can clean, when it needs extra memory for the String.
str = func2_return_big_string_2();

更新:为了支持我的主张,我写了一个测试用例,证明我是对的:http://ideone.com/BwGfSN。 代码演示了(使用Finalizer)之间的区别:

GCTest test;
// Without the null assignment
test = create(0);
test = create(1);
test = null;
System.gc();

try {Thread.sleep(10);} catch (Exception e){}
System.out.println();

// With the null assignment
test = create(2);
test = null;
test = create(3);
test = null;
System.gc();

答案 4 :(得分:1)

我刚刚找到MutableString实现。它可以在Maven Central中找到。以下是他们的JavaDoc页面的摘录:

  
      
  • 可变字符串占用的空间很小 - 它们唯一的属性是后备字符数组和整数;
  •   
  • 他们的方法尽可能高效:例如,如果对数组访问的限制暗示了对参数的某些限制,我们不会明确检查它,并且Bloom过滤器用于加速多字符替换;
  •   
  • 他们允许您直接访问支持阵列(风险自负);
  •   
  • 它们实现CharSequence,,因此,例如,您可以使用标准Java API将可变字符串与正则表达式匹配或拆分;
  •   
  • 他们实施Appendable,因此可以与Formatter和类似的类一起使用;
  •   

<强>更新

您可以利用此Appendable的{​​{1}}接口来读取几乎为零内存开销的文件(8KB,这是Java中的默认缓冲区大小)。使用Guava的CharStreams.copy,它看起来像这样:

MutableString

Full working example