使用java.lang.String.intern()是不错的做法吗?
有关String.intern()
的Javadoc没有提供太多细节。 (简而言之:它返回string的标准表示,允许使用==
来比较string间的比较)
- 我什么时候可以使用这个函数来支持
String.equals()
? - 有没有在Javadoc中提到的副作用,即通过JIT编译器或多或less的优化?
- 是否还有
String.intern()
用法?
我什么时候可以使用这个函数来支持String.equals()
当你需要速度,因为你可以通过引用比较string(==比等于)
Javadoc中没有提到的副作用?
主要的缺点是你必须记住要确保你实际上做intern()所有你要比较的string。 忘记实习()所有的string很容易,然后你可能会混淆不正确的结果。 另外,为了大家的利益,请务必清楚地logging你依靠内部化的string。
如果你决定内部化string的第二个缺点是intern()方法是相对昂贵的。 它必须pipe理独特的string池,因此它可以完成一些工作(即使string已经被内化)。 所以,在你的代码devise中要小心,这样你就可以在intern()input所有合适的string,所以你不必担心它了。
(来自JGuru)
第三个缺点(Java 7或更less):internedstring居住在PermGen空间,通常很小; 你可能遇到一个OutOfMemoryError与大量的免费堆空间。
(来自Michael Borgwardt)
这(几乎)与string比较没有任何关系。 stringinterning用于保存内存,如果在应用程序中有许多string具有相同的内容。 通过使用String.intern()
,应用程序在长时间内只有一个实例,副作用是可以执行快速引用相等比较而不是普通的string比较(但是这通常不可取,因为它很容易中断忘记只实习一个实例)。
String.intern()
绝对是在现代JVM中收集的垃圾。
由于GC活动,以下NEVER内存不足:
// java -cp . -Xmx128m UserOfIntern public class UserOfIntern { public static void main(String[] args) { Random random = new Random(); System.out.println(random.nextLong()); while (true) { String s = String.valueOf(random.nextLong()); s = s.intern(); } } }
查看更多(从我) 非GCed String.intern()的神话 。
我最近写了一篇关于Java 6,7和8中的String.intern()实现的文章: Java 6,7和8中的String.intern – string池 。
我希望它应该包含足够的关于在Java中使用string池的情况的信息。
简而言之:
- 避免Java 6中的
String.intern()
,因为它进入了PermGen - 在Java 7和Java 8中优先使用
String.intern()
:与使用自己的对象池相比,它使用的内存less4-5倍 - 请务必调整
-XX:StringTableSize
(默认值可能太小;设置素数)
用==比较string比用equals()要快得多
5时间更快,但由于string比较通常只占应用程序总执行时间的一小部分,所以总体收益要小得多,而最终的收益将被稀释到百分之几。
String.intern()将string从堆中取出并放入PermGen中
string内在化被放置在不同的存储区域: 永久生成是为非用户对象(如类,方法和其他内部JVM对象)保留的JVM区域。 这个地区的规模是有限的,是比宝贵的宝贵。 由于这个区域比堆小,因此使用所有空间的可能性更大,并且得到OutOfMemoryException。
String.intern()string被垃圾回收
在JVM的新版本中,内部化string在没有被任何对象引用时也会被垃圾回收。
记住以上三点,你可以推断String intern()只有在你进行大量string比较的情况下才有用,但是如果你不知道你究竟是什么,那么最好不要使用内部string是做 …
我没有意识到任何优点,如果有人会认为equals()会自己使用intern()在内部(不这样做)。
打破实习生()的神话
我什么时候可以使用这个函数来支持String.equals()
鉴于他们做不同的事情,可能永远不会。
由于性能原因实习string,以便您可以比较它们的参考相等性,只有在持续引用string一段时间时才会有好处 – 来自用户input或IO的string将不会被实施。
这意味着在你的应用程序中,你从外部源接收input并将其处理成具有语义值的对象 – 标识符表示 – 但是该对象具有与原始数据不可区分的types,并且对于程序员应该如何用它。
创build一个被实现的UserId
types(创build一个线程安全的generics实现机制很容易)并且像一个开放的枚举一样,比使用引用语义重载java.lang.String
types(如果碰巧是用户ID。
这样你就不会在特定的string是否被实现之间混淆,并且你可以在打开的枚举中封装你需要的任何额外的行为。
有没有在Javadoc中提到的副作用,即通过JIT编译器或多或less的优化?
我不知道JIT的级别,但是对string池有直接的字节码支持 ,这是通过一个专用的CONSTANT_String_info
结构(不同于大多数具有更多通用表示的对象)神奇而高效地实现的。
JVMS
JVMS 7 5.1说 :
string文字是对类String的一个实例的引用,并且是从类或接口的二进制表示中的CONSTANT_String_info结构(§4.4.3)派生的。 CONSTANT_String_info结构给出了构成string文字的Unicode代码点序列。
Java编程语言要求相同的string文字(即包含相同的代码点序列的文字)必须引用同一类String(JLS§3.10.5)的实例。 此外,如果在任何string上调用String.intern方法,则结果是对同一个类实例的引用,如果该string显示为文字,则会返回该实例。 因此,以下expression式必须具有真值:
("a" + "b" + "c").intern() == "abc"
为了派生string文字,Java虚拟机检查由CONSTANT_String_info结构给出的代码点序列。
如果方法String.intern先前在包含与CONSTANT_String_info结构相同的Unicode代码点序列的String类实例上调用,则string文字派生的结果是对类String相同实例的引用。
否则,将创build一个包含由CONSTANT_String_info结构给出的Unicode代码点序列的类String的新实例; 该类实例的引用是string文字派生的结果。 最后,调用新的String实例的intern方法。
字节码
查看OpenJDK 7上的字节码实现也是有益的。
如果我们反编译:
public class StringPool { public static void main(String[] args) { String a = "abc"; String b = "abc"; String c = new String("abc"); System.out.println(a); System.out.println(b); System.out.println(a == c); } }
我们有不断的池:
#2 = String #32 // abc [...] #32 = Utf8 abc
main
:
0: ldc #2 // String abc 2: astore_1 3: ldc #2 // String abc 5: astore_2 6: new #3 // class java/lang/String 9: dup 10: ldc #2 // String abc 12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 15: astore_3 16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 19: aload_1 20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_2 27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 33: aload_1 34: aload_3 35: if_acmpne 42 38: iconst_1 39: goto 43 42: iconst_0 43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
请注意:
-
0
和3
:加载相同的ldc #2
常量(文字) -
12
:创build一个新的string实例(以#2
作为参数) -
35
:将a
和c
作为常规对象与if_acmpne
进行比较
常量string的表示在字节码上非常神奇:
- 它有一个专用的CONSTANT_String_info结构,不像常规对象(例如
new String
) - 该结构指向包含数据的CONSTANT_Utf8_info结构 。 这是表示string的唯一必要数据。
而上面的JVMS报价似乎表示,只要Utf8指向的是相同的,则相同的实例由ldc
加载。
我已经做了类似的testing领域,并且:
-
static final String s = "abc"
通过ConstantValue属性指向常量表 - 非final字段没有该属性,但仍可以使用
ldc
进行初始化
奖金 :比较那整体池 ,没有直接的字节码支持(即没有CONSTANT_String_info
模拟)。
我会检查intern和== – 比较,而不是等于只是在比较是多重比较string的瓶颈的情况下。 这是不太可能帮助less量的比较,因为intern()不是免费的。 积极实习string后,你会发现intern()的调用越来越慢。
一种内存泄漏可能来自使用subString()
当结果比源string小,且对象具有较长的使用寿命时。
正常的解决scheme是使用new String( s.subString(...))
但是当你有一个潜在的/可能的子subString(...)
的结果存储和无法控制调用者的类,你可能会考虑存储传递给构造函数的String参数的intern()
。 这释放了潜在的大缓冲区。
在equals()
方法经常被调用的情况下,String interning非常有用,因为equals()
方法会快速检查方法开始处的对象是否相同。
if (this == anObject) { return true; }
这通常发生在searchCollection
但其他代码也可能会执行string相等性检查。
实习生需要付出一定的代价,但是我做了一些代码的微观基准,发现实习过程增加了10倍的运行时间。
进行实习的最佳地点通常是当您阅读存储在代码之外的密钥时,因为代码中的string会被自动截取。 这通常会发生在您的应用程序的初始阶段,以防止第一用户的处罚。
另一个可以完成的地方是处理用户input,可以用来进行密钥查找。 这通常发生在您的请求处理器中,请注意internedstring应该传递下来。
除此之外,在代码的其余部分做实习并没有太大意义,因为它通常不会带来任何好处。
我会投票给它不值得维护的麻烦。
大多数时候,没有必要,没有性能好处,除非你的代码在子string方面做了很多工作。 在这种情况下,String类将使用原始string加上偏移量来节省内存。 如果你的代码使用了很多子string,那么我怀疑它只会导致你的内存需求爆炸。
http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html
断言String.equals()
使用"=="
来比较String
对象之前,根据
http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
它比较string的长度,然后是内容。
(顺便说一下,销售目录中的产品代码string可能都是一样的长度 – BIC0417是一个骑自行车的安全头盔,TIG0003是一个活的成年雄虎 – 你可能需要各种许可证来订购其中的一个。也许你最好同时订购安全帽。)
所以听起来好像你从intern()
版本中replace了你的string,但是你的安全性和易读性,以及标准的兼容性,在编程中equals()
没有使用“==”。 而且,我要说的大部分事情都取决于事实,如果事实是真的。
但是在使用"=="
之前, String.equals()
testing了你传递了一个string而不是其他对象? 我没有资格说,但我猜不会,因为压倒性多数这样的equals()
操作将string转换为string,所以几乎总是通过testing。 实际上,在String.equals()
“==”的优先级意味着您经常将string与相同的实际对象进行比较。
我希望没有人会感到惊讶,下面这几行会产生“错误”的结果:
Integer i = 1; System.out.println("1".equals(i));
但是如果你在第二行把i
改为i.toString()
,当然是true
。
您可能希望从实习中受益的场所包括Set
和Map
。 我希望internedstring有他们的hashcodecaching…我认为这将是一个要求。 我希望我没有放弃一个可以赚到一百万美元的想法。 🙂
至于内存,如果你的string数量很大,或者如果你想让程序代码使用的内存很小,这也是一个重要的限制。 如果-distinct-Strings的数量非常大,那么可能是时候考虑使用专用的数据库程序代码来pipe理它们,并且需要一个单独的数据库服务器。 同样,如果你可以改进一个小程序(需要在10000个实例中同时运行),通过让它不存储它自己的string本身。
创build一个新的string,然后立即放弃它的intern()
替代品,但是没有一个明确的select,除了保留重复的string。 所以真正的执行成本是在intern池中search你的string,然后让垃圾收集器处理原始的。 如果它是一个string文字,那么它已经来到国内已经无论如何。
我想知道是否intern()
可以被恶意程序代码滥用,以检测是否有一些string及其对象引用已经存在intern()
池,因此存在于Java会话中的其他地方,当不应该知道。 但是,只有当程序代码已经以可信的方式被使用时,这才是可能的。 不过,还需要考虑一下您的程序中包含的第三方库,用于存储和logging您的ATM PIN号码!
丹尼尔布鲁克纳是绝对正确的。 stringinterning是为了节省内存(堆)。 我们的系统目前有一个巨大的hashmap来保存某些数据。 随着系统规模的扩大,哈希映射将足够大,使堆内存不足(正如我们testing过的)。 通过将所有重复的string实例化到hashmap中的所有对象,它为我们节省了大量的堆空间。
同样在Java 7中,intermstring不会在PermGen中长期存在,而是堆。 所以你不必担心它的大小,是的,它会收集垃圾:
在JDK 7中,internedstring不再分配在Java堆的永久生成中,而是分配在Java堆的主要部分(称为年轻人和老一代)中,以及由应用程序创build的其他对象。 这种改变将导致更多的数据驻留在主Java堆中,永久代中的数据更less,因此可能需要调整堆大小。 由于这种变化,大多数应用程序在堆使用中只会看到相对较小的差异,但是加载很多类或大量使用String.intern()方法的较大应用程序将会看到更显着的差异。
实习生的真正原因不在于此。 出现内存不足错误后,您可以使用它。 在一个典型的程序中,很多string是其他大string的String.substring()[想想从一个100K的xml文件中取出一个用户名。 java的实现是,子string保存对原始string的引用,并在该string中包含start + end。 (它背后的想法是重复使用同一个大string)
在1000个大文件之后,您只能保存1000个短名称,您将在内存中保存全部1000个文件! 解决scheme:在这种情况下,只需使用smallsubstring.intern()
我正在使用intern来节省内存,我在内存中保存了大量的String数据,并且使用intern()来保存了大量的内存。 不幸的是,虽然使用的内存不多,但是它使用的内存却存储在PermGen内存中,而不是堆,而且很难向客户解释如何增加这种内存的分配。
那么是否有一个替代intern()来减less内存消耗,(对==等于性能好处不是我的一个组织)
让我们面对现实吧:主要的用例场景是读取数据stream时(通过inputstream或JDBC ResultSet),并且遍及整个过程中有许多小string。
下面是一个小技巧,可以让您对使用什么样的机制来内化string和其他不可变对象以及示例实现有一些控制。
/** * Extends the notion of String.intern() to different mechanisms and * different types. For example, an implementation can use an * LRUCache<T,?>, or a WeakHashMap. */ public interface Internalizer<T> { public T get(T obj); } public static class LRUInternalizer<T> implements Internalizer<T> { private final LRUCache<T, T> cache; public LRUInternalizer(int size) { cache = new LRUCache<T, T>(size) { private static final long serialVersionUID = 1L; @Override protected T retrieve(T key) { return key; } }; } @Override public T get(T obj) { return cache.get(obj); } } public class PermGenInternalizer implements Internalizer<String> { @Override public String get(String obj) { return obj.intern(); } }
当我从stream中读取字段或从ResultSet读取字段时,我经常使用它。 注意: LRUCache
是一个基于LinkedHashMap<K,V>
的简单caching。 它会自动调用用户提供的所有caching未命中的retrieve()
方法。
使用这种方法是在读取(或读取)之前创build一个LRUInternalizer
,使用它来内化string和其他小的不可变对象,然后释放它。 例如:
Internalizer<String> internalizer = new LRUInternalizer(2048); // ... get some object "input" that stream fields for (String s : input.nextField()) { s = internalizer.get(s); // store s... }
我正在使用它来caching链接到相关名称的大约36000个代码的内容。 我在caching中实习string,因为许多代码指向相同的string。
通过在caching中实现string,我确保指向相同string的代码实际上指向相同的内存,从而节省了RAM空间。
如果被截取的string实际上是垃圾收集的,它根本就不适合我。 这基本上会否定实习的目的。 我不会收集垃圾,因为我持有对caching中每个string的引用。
实习string的成本比单个stringA(E)(B)的比较节省的时间要多得多。 只有在重复使用未改变的stringvariables时才使用它(出于性能原因)。 例如,如果你经常迭代一个稳定的string列表来更新某些键入相同string字段的地图,你可以得到很好的保存。
我build议使用stringinterning来调整性能,当你优化你的代码的特定部分。
还要记住,string是不可改变的,不要做出愚蠢的错误
String a = SOME_RANDOM_VALUE a.intern()
记得去做
String a = SOME_RANDOM_VALUE.intern()
如果你正在寻找一个无限的替代String.intern,也垃圾收集,以下工作对我来说很好。
private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>(); public static String internalize(String k) { synchronized (internStrings) { WeakReference<String> weakReference = internStrings.get(k); String v = weakReference != null ? weakReference.get() : null; if (v == null) { v = k; internStrings.put(v, new WeakReference<String>(v)); } return v; } }
当然,如果你可以粗略地估计出有多less个不同的string,那么只需使用String.intern()和-XX:StringTableSize = highEnoughValue即可 。