在有人质疑使用string.intern()
的事实之前,让我说出于内存和性能原因,我需要在我的特定应用程序中使用它。 [1]
所以,直到现在我使用String.intern()
并认为这是最有效的方法。但是,我注意到它已经成为软件的瓶颈。 [2]
然后,就在最近,我尝试用一个巨大的地图替换String.intern()
,我放置/获取字符串,以便每次获得一个唯一的实例。我预计这会慢一点......但恰恰相反!它速度非常快!通过推送/轮询地图(实现完全相同)来替换intern()
导致速度提高了一个数量级。
问题是:为什么intern()
这么慢?!?那么为什么它不是简单地由地图(实际上只是一个定制的集合)备份而且速度会快得多?我很困惑。
[1]:对于不相信的人:它是在自然语言处理中并且必须处理千兆字节的文本,因此需要避免相同字符串的许多实例以避免炸毁内存和参考字符串比较足够快
[2]:没有它(正常的字符串)是不可能的,有了它,这个特定的步骤仍然是计算密集度最高的一个
修改
由于对这篇文章的兴趣,这里有一些代码来测试它:
实习结果超过100万字符串:
HashMap
:4秒String.intern()
:54秒由于避免了一些预热/操作系统IO缓存和类似的事情,通过颠倒两个基准的顺序重复实验:
String.intern()
:69秒HashMap
:3秒如你所见,差异非常显着,超过十倍。 (使用OpenJDK 1.6.0_22 64位...但是使用太阳一个导致类似的结果我认为)
答案 0 :(得分:6)
此article讨论String.intern()
的实施。在Java 6和7中,实现使用固定大小(1009)哈希表,因此数字条目增长,性能变为O(n)。可以使用-XX:StringTableSize=N
更改固定大小。显然,在Java8中,默认大小较大但问题仍然存在。
答案 1 :(得分:3)
性能差异的最可能原因是:String.intern()
是一种本机方法,调用本机方法会产生大量开销。
那么为什么它是一种原生方法呢?可能是因为它使用常量池,这是一个低级VM构造。
答案 2 :(得分:3)
intern()不同步,至少在Java语言级别。
我认为您的意思是String.intern()
方法未在String类的源代码中声明为synchronized
。事实上,这是一个真实的陈述。
然而:
将intern()
声明为synchronized
只会锁定当前的String实例,因为它是一个实例方法,而不是静态方法。所以他们无法以这种方式实现字符串池同步。
如果您退一步考虑一下,字符串池必须执行某种内部同步。如果没有,它将在多线程应用程序中无法使用,因为对于使用intern()
方法进行外部同步的所有代码,根本没有实用的方法。
因此,字符串池执行的内部同步可能成为使用intern()
的多线程应用程序的瓶颈。
答案 3 :(得分:1)
我无法说出任何有关它的经验,但是来自String docs:
“当调用intern方法时,如果池已经包含与{{link #equals(Object)}方法确定的此String
对象相等的字符串,则返回池中的字符串。否则,将此String
对象添加到池中,并返回对此String
对象的引用。“
当处理大量对象时,任何涉及散列的解决方案都会胜过没有散列的解决方案。我想你只是看到了滥用Java语言功能的结果。实习不是作为您使用的字符串映射。你应该使用Map(或适当的Set)。 String表用于语言级别的优化,而不是应用级别。
答案 4 :(得分:-1)
接受的答案是错误的。字符串变慢是因为两个原因:
1. -XX:StringTableSize限制。
在java中,它使用内部哈希表来管理字符串缓存,在java 6中,默认的StringTableSize值是1009,这意味着当创建越来越多的字符串对象时,string.intern是O(字符串对象的数量/ 1009),它变得越来越慢。
\ openjdk7 \热点\ SRC \共享\ VM \类文件\ 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大小相对较小。