0

Java String 是不可变的,所以

当您创建一个字符串时,会在堆中为其分配一块内存,当您更改其值时,会为该字符串创建一个新的内存块,并且旧的内存块有资格进行垃圾回收,例如

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

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

有没有办法让大字符串 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;
}
4

5 回答 5

2

我看到几个选项:

  1. 使用char[].
  2. 使用公共可重用缓冲区复制StringBuilder到您的版本中。MyStringBuilder主要缺点是它缺少正则表达式。当我需要提高性能时,我就是这样做的。
  3. Hack for JDK <=6:有一个受保护的构造函数来重用字符串/包装字符缓冲区。JDK 7+ 不再存在。对此需要非常谨慎,一旦你有 C/C++ 背景,这不是问题。
  4. 使用公共可重用缓冲区复制String到 中。MutableString我认为添加自定义正则表达式匹配器不会有问题,因为它们有很多可用。
于 2013-10-20T21:05:29.377 回答
2

使用 StringBuffer,StringBuffer.append()

于 2013-10-20T16:20:06.803 回答
1

对于非实习字符串来说,这并不重要。如果您开始耗尽内存,垃圾收集器将删除不再引用的所有对象。

Interned Strings 更难收集,有关详细信息,请参阅String literals 的垃圾收集

编辑一个非实习字符串就像一个普通的对象。一旦不再引用它,它将被垃圾收集。

如果str是唯一指向原始字符串的引用并且str被更改为指向其他东西,那么原始字符串有资格进行垃圾收集。因此,您不再需要担心内存不足,因为如果需要内存,JVM 会收集它。

于 2013-10-20T16:24:29.397 回答
1

我刚刚找到了一个MutableString实现。它在Maven Central中可用。这是他们的 JavaDoc 页面的摘录:

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

更新

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

MutableString str = new MutableString((int) file.length());
CharStreams.copy(Files.newReaderSupplier(file, Charset.defaultCharset()), str);
System.out.println(str);

完整的工作示例

于 2013-10-21T13:11:52.363 回答
1

为了避免在内存中同时存在新旧字符串,您可以通过分配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。该代码演示了(使用终结器)之间的区别:

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();
于 2013-10-20T21:09:55.487 回答