很多人都在谈论String.intern()的性能优势,但我实际上对性能损失更感兴趣。
我主要担心的是:
我担心所有这些事情,因为我目前正在研究一个财务应用程序,因为重复的字符串会导致使用太多内存。有些字符串基本上看起来像枚举值,并且只能有有限数量的潜在值(例如货币名称(“USD”,“EUR”))存在超过一百万份。在这种情况下,String.intern()看起来很简单,但我担心每次在某处存储货币时调用intern()的同步开销。
除此之外,其他一些类型的字符串可以有数百万个不同的值,但每个字符串仍然有数万个副本(例如ISIN代码)。对于这些,我担心实际上一百万字符串会基本上减慢intern()方法的速度,从而使我的应用程序陷入困境。
答案 0 :(得分:36)
我自己做了一些基准测试。对于搜索成本部分,我决定将String.intern()与ConcurrentHashMap.putIfAbsent(s,s)进行比较。基本上,这两个方法做同样的事情,除了String.intern()是一个本机方法,它存储和读取直接在JVM中管理的SymbolTable,而ConcurrentHashMap.putIfAbsent()只是一个普通的实例方法。
您可以在github gist上找到基准代码(因为没有更好的位置)。您还可以在源文件顶部的注释中找到我在启动JVM时使用的选项(以验证基准测试没有偏差)。
无论如何这里是结果:
<强>图例强>
<强>的String.intern()强>
count initial intern lookup same string lookup equal string
1'000'000 40206 34698 35000
400'000 5198 4481 4477
200'000 955 828 803
100'000 234 215 220
80'000 110 94 99
40'000 52 30 32
20'000 20 10 13
10'000 7 5 7
<强> ConcurrentHashMap.putIfAbsent()强>
count initial intern lookup same string lookup equal string
1'000'000 411 246 309
800'000 352 194 229
400'000 162 95 114
200'000 78 50 55
100'000 41 28 28
80'000 31 23 22
40'000 20 14 16
20'000 12 6 7
10'000 9 5 3
搜索成本的结论:String.intern()的调用成本高得惊人。它在O(n)中非常严重,其中n是池中字符串的数量。当池中的字符串数量增加时,从池中查找一个字符串的时间会增长得更多(每次查找0.7微秒,10'000个字符串,每次查找40微秒,1'000'000个字符串)。
ConcurrentHashMap按预期进行扩展,池中的字符串数量对查找速度没有影响。
根据这个实验,我强烈建议如果要实习多个字符串,请避免使用String.intern()。
答案 1 :(得分:21)
我最近在Java 6,7和8中写了一篇关于String.intern()实现的文章: String.intern in Java 6, 7 and 8 - string pooling
有一个-XX:StringTableSize JVM参数,它允许你使String.intern在Java7 +中非常有用。所以,不幸的是,我不得不说这个问题目前正在向读者提供误导性的信息。
答案 2 :(得分:5)
我发现使用fastutil哈希表并进行自己的实习而不是重用String.intern()
会更好。使用我自己的哈希表意味着我可以自己做出关于并发性的决定,而且我不会竞争PermGen空间。
我这样做是因为我正在处理一个问题,因为它有数百万字符串,许多相同,我想(a)减少占地面积和(b)允许通过身份进行比较。对于我的问题,使用我的不 String.intern()
方法,实习比使用实际更好。
因人而异。
答案 3 :(得分:0)
String.intern变慢的原因有两个:
1. -XX:StringTableSize限制。
在Java中,它使用内部哈希表来管理字符串缓存,在Java 6中,默认的StringTableSize值为1009,这意味着string.intern为O(字符串对象的数量/ 1009),当创建越来越多的字符串对象时,它变得越来越慢。
\ openjdk7 \ hotspot \ src \ share \ vm \ classfile \ symbolTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
unsigned int hashValue = java_lang_String::hash_string(name, len);
int index = the_table()->hash_to_index(hashValue);
oop string = the_table()->lookup(index, name, len, hashValue);
// Found
if (string != NULL) return string;
// Otherwise, add to symbol to table
return the_table()->basic_add(index, string_or_null, name, len,
hashValue, CHECK_NULL);
}
2。在Java 6中,字符串缓存池位于perm区域中,而不是在堆中,大多数时候,我们将perm的大小配置为相对较小。
答案 4 :(得分:-1)
以下微基准测试建议使用枚举提供大约十倍的性能改进(通常的微基准警告适用)测试代码如下:
public class Test {
private enum E {
E1;
private static final Map<String, E> named = new HashMap<String, E>();
static {
for (E e : E.values()) {
named.put( e.name(), e );
}
}
private static E get(String s) {
return named.get( s );
}
}
public static void main(String... strings) {
E e = E.get( "E1" ); // ensure map is initialised
long start = System.nanoTime();
testMap( 10000000 );
long end = System.nanoTime();
System.out.println( 1E-9 * (end - start) );
}
private static void testIntern(int num) {
for (int i = 0; i < num; i++) {
String s = "E1".intern();
}
}
private static void testMap(int num) {
for (int i = 0; i < num; i++) {
E e = E.get( "E1" );
}
}
}
结果(1000万次迭代): testIntern() - 0.8秒 testMap() - 0.06秒
当然是YMMV,但是枚举提供了比Strings更多的好处...类型安全性超过其他随机字符串,添加方法的能力等似乎是最好的方式去imho