我的应用程序是多线程且涉及大量字符串处理。我们遇到了过度的内存消耗问题,并且分析表明这是由于字符串数据造成的。我认为使用某种飞行权重模式实现甚至缓存(我知道字符串经常被复制,尽管在这方面我没有任何硬数据)将极大地受益于内存消耗。
我查看了Java常量池和String.intern,但似乎会引起PermGen问题。
在Java中,实现全局、多线程字符串池的最佳替代方案是什么?
编辑:还请查看我之前的相关问题:Java在幕后如何实现字符串的飞行权重模式?
我的应用程序是多线程且涉及大量字符串处理。我们遇到了过度的内存消耗问题,并且分析表明这是由于字符串数据造成的。我认为使用某种飞行权重模式实现甚至缓存(我知道字符串经常被复制,尽管在这方面我没有任何硬数据)将极大地受益于内存消耗。
我查看了Java常量池和String.intern,但似乎会引起PermGen问题。
在Java中,实现全局、多线程字符串池的最佳替代方案是什么?
编辑:还请查看我之前的相关问题:Java在幕后如何实现字符串的飞行权重模式?
注意:本答案使用的示例可能与现代运行时JVM库无关。特别是,在OpenJDK/Oracle 7+中,substring示例不再是问题。
我知道这与人们通常告诉你的相反,但有时明确创建新的String
实例可以显著减少内存使用。
由于字符串是不可变的,因此几种方法利用了这一事实并共享支持字符数组以节省内存。然而,偶尔这实际上会增加内存,因为它阻止了那些数组未使用部分的垃圾回收。
例如,假设您正在解析日志文件的消息ID以提取警告ID。您的代码将类似于:
//Format:
//ID: [WARNING|ERROR|DEBUG] Message...
String testLine = "5AB729: WARNING Some really really really long message";
Matcher matcher = Pattern.compile("([A-Z0-9]*): WARNING.*").matcher(testLine);
if ( matcher.matches() ) {
String id = matcher.group(1);
//...do something with id...
}
但是看看实际存储的数据:
//...
String id = matcher.group(1);
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] data = ((char[])valueField.get(id));
System.out.println("Actual data stored for string \"" + id + "\": " + Arrays.toString(data) );
这是整个测试行,因为匹配器只会将一个新的字符串实例包装在相同的字符数据周围。比较替换 String id = matcher.group(1);
与 String id = new String(matcher.group(1));
的结果。
length()
返回的结果,“size”的大小为1。但是这两个字符串引用了同一个底层字符数组,只是记录了不同的开始和结束索引,指向字符串所引用的数组。字符数组没有复制到新的字符串中;只有对数组的引用被复制到新的字符串中。 - Mark Petersnew String
。String s1 = new String("foo");
String s2 = new String("foo");
String s1 = "foo";
String s2 = "foo";
这将在堆中创建一个实例,两者将引用相同的实例(作为证据,s1 == s2
在此处将返回true
)。
在循环中不要使用+=
来连接字符串:
String s = "";
for (/* some loop condition */) {
s += "new";
}
+=
每次都会在堆中隐式创建一个 new String
。建议使用其他方式。StringBuilder sb = new StringBuilder();
for (/* some loop condition */) {
sb.append("new");
}
String s = sb.toString();
如果您需要进行“大量字符串处理”,建议使用StringBuilder
或其同步版本StringBuffer
,可以提供用于此类目的有用方法,例如append()
、insert()
、delete()
等。另外,可以参考它的javadoc。
StringBuilder
。 - BalusC如果你按照被接受的答案所说,使用了Java 7或更新版本,那么你并没有按照它所说的去做。
subString()
方法的实现已经发生了改变。
永远不要编写依赖于可能会发生巨大变化并且如果依赖于旧的行为可能会使情况变得更糟的实现的代码。
1950 public String substring(int beginIndex, int endIndex) {
1951 if (beginIndex < 0) {
1952 throw new StringIndexOutOfBoundsException(beginIndex);
1953 }
1954 if (endIndex > count) {
1955 throw new StringIndexOutOfBoundsException(endIndex);
1956 }
1957 if (beginIndex > endIndex) {
1958 throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
1959 }
1960 return ((beginIndex == 0) && (endIndex == count)) ? this :
1961 new String(offset + beginIndex, endIndex - beginIndex, value);
1962 }
如果您在Java 7或更新版本中使用被接受的答案,则会创建两倍于实际需要的内存使用量和垃圾,这些垃圾需要进行收集。
高效地在内存中压缩字符串!我曾经编写过一个超级内存高效的Set类,其中字符串被存储为一棵树。如果通过遍历字母到达叶子节点,则该条目包含在集合中。使用起来也很快,并且非常适合存储大型词典。
而且不要忘记,在我测试的几乎每个应用程序中,字符串通常是内存中最大的部分,因此如果您需要它们,请不要忽略它们。
示例:
您有3个字符串:Beer、Beans和Blood。您可以创建如下的树形结构:
B
+-e
+-er
+-ans
+-lood
你正在处理的文本是自由格式的,还是输入必须遵循严格的规范?如果你的大部分文本可以归约为一组固定的可能值,那么一个TSE(文本转换引擎)可以在这里帮助你,并且可以为你的信息添加上下文/语义。这样可以在创建信息的时候就赋予其含义,而不是在使用的时候才去解读。