关于Javadoc的内容String.intern()
不多。(简而言之:它返回字符串的规范表示形式,从而允许使用来比较被嵌入的字符串==
)
- 什么时候有利于使用此功能
String.equals()
? - 是否存在Javadoc中未提及的副作用,即JIT编译器或多或少的优化?
- 还有其他用途
String.intern()
吗?
关于Javadoc的内容String.intern()
不多。(简而言之:它返回字符串的规范表示形式,从而允许使用来比较被嵌入的字符串==
)
String.equals()
?String.intern()
吗?Answers:
我何时会使用此函数来支持String.equals()
当您需要速度时,因为可以按引用比较字符串(==比等于快)
是否有Javadoc中未提及的副作用?
主要缺点是您必须记住要确保实际上要对要比较的所有字符串执行intern()。忘记intern()所有字符串很容易,然后您会得到令人困惑的错误结果。另外,为了大家的缘故,请确保非常清楚地证明您所依赖的是内部化的字符串。
如果您决定内部化字符串的第二个缺点是intern()方法相对昂贵。它必须管理唯一的字符串池,因此需要做很多工作(即使字符串已经被内部化了)。因此,在代码设计中要格外小心,例如,在输入时使用intern()所有合适的字符串,这样就不必担心它了。
(来自JGuru)
第三个缺点(仅限Java 7或更低版本):实习字符串生活在PermGen空间中,该空间通常很小;您可能会遇到带有大量可用堆空间的OutOfMemoryError。
(摘自Michael Borgwardt)
if (s1.equals(s2))
和之间的差异if (i1 == i2)
很小,除非您有很多长字符串相同的前导字符。在大多数实际用途中(URL除外),字符串在前几个字符内会有所不同。无论如何,长的if-else链仍然是代码的味道:使用枚举和函子映射。
这(几乎)与字符串比较无关。字符串实习的目的是保存记忆,如果你有在你的应用程序相同的内容许多字符串。通过使用String.intern()
该应用程序,从长远来看将只有一个实例,并且副作用是您可以执行快速引用相等比较而不是普通的字符串比较(但这通常是不可取的,因为通过忘记仅使用intern确实很容易破坏它)一个实例)。
str.intern()
在str
is 以后再使用"Hello"
。
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()的神话中了解更多(来自我)。
OutOfMemoryException
-不,不是上面的代码,在我的脑海:链接到javaturning文章,该文章指向此文章,指向javaturning文章,其中... :-)
我最近写了一篇有关Java 6、7和8中String.intern()实现的文章: Java 6、7和8字符串池中的String.intern。
我希望它应该包含有关Java字符串池当前情况的足够信息。
简而言之:
String.intern()
在Java 6中使用,因为它已包含在PermGen中String.intern()
用Java 7的Java 8:它使用较少的4-5倍的内存比滚动自己的对象池-XX:StringTableSize
(默认值可能太小;设置质数)用==比较字符串比用equals()比较字符串快得多
5时间更快,但是由于字符串比较通常只占应用程序总执行时间的一小部分,因此总收益要小得多,最终收益将被稀释到百分之几。
String.intern()将字符串从Heap中拉出并放入PermGen中
内部化的字符串放在不同的存储区域中:永久生成,这是JVM的一个区域,保留给非用户对象,例如类,方法和其他内部JVM对象。这个区域的大小是有限的,比堆大得多。由于此区域小于Heap,因此更有可能使用所有空间并获得OutOfMemoryException。
String.intern()字符串被垃圾回收
在新版本的JVM中,当未由任何对象引用时,也会对内部化的字符串进行垃圾回收。
请牢记以上三点,您可以推断出String intern()仅在您进行大量字符串比较时仅在少数情况下才有用,但是最好不要使用内部字符串,如果您不确切知道自己在做什么。是做 ...
我何时会使用此函数来支持String.equals()
鉴于他们做不同的事情,可能永远不会。
出于性能原因而对字符串进行internation,以便您可以比较它们是否具有引用相等性,这仅在您保留对字符串的引用一段时间时才有好处-来自用户输入或IO的字符串不会被intern。
这意味着在您的应用程序中,您将接收来自外部源的输入,并将其处理为具有语义值的对象(例如标识符),但是该对象的类型与原始数据没有区别,并且对于程序员应该如何使用具有不同的规则用它。
创建一个内部UserId
类型(创建一个线程安全的通用内部机制很容易)并且像一个开放的枚举,几乎总是比java.lang.String
使用引用语义重载该类型(如果它恰好是用户ID)更好。
这样一来,您就不会在特定的String是否已被插入之间感到困惑,并且可以将所需的任何其他行为封装在开放枚举中。
我没有意识到任何优势,如果有人认为,equals()本身会在内部使用intern()(实际上没有)。
intern
,并很好的理由equals
不默认这样做。您发布的链接是完整的错误提示。最后一段甚至承认intern
有一个有效的使用场景:繁重的文本处理(例如解析器)。得出结论:“ [XYZ]如果您不知道自己在做什么就很危险”,这太平庸了,以至于身体受到伤害。
丹尼尔·布鲁克纳(DanielBrückner)绝对正确。字符串实习是为了节省内存(堆)。我们的系统目前有一个用于保存某些数据的巨型哈希图。随着系统的扩展,哈希图将足够大以使堆内存不足(如我们所测试)。通过遍历哈希图中所有对象的所有重复字符串,它为我们节省了大量的堆空间。
同样在Java 7中,实习生字符串不再存在于PermGen中,而是堆中。因此,您不必担心它的大小,是的,它可以收集垃圾:
在JDK 7中,不再将永久字符串分配给Java堆的永久代,而是分配给Java堆的主要部分(称为年轻代和旧代),以及由应用程序创建的其他对象。 。此更改将导致更多数据驻留在主Java堆中,而永久生成中的数据更少,因此可能需要调整堆大小。由于此更改,大多数应用程序只会看到相对较小的堆使用差异,但是加载许多类或大量使用String.intern()方法的大型应用程序将看到更大的差异。
String
实例使用了大部分堆空间。查看它们的内容时,我看到很多重复项,并决定切换到intern()
,从而节省了数百MB。
是否存在Javadoc中未提及的副作用,即JIT编译器或多或少的优化?
我不了解JIT级别,但是对字符串池有直接的字节码支持,该字符串池通过专用CONSTANT_String_info
结构神奇而有效地实现了(不同于大多数其他具有更通用表示形式的对象)。
字符串文字是对String类实例的引用,它是从CONSTANT_String_info结构(第4.4.3节)以类或接口的二进制表示形式派生的。CONSTANT_String_info结构给出了构成字符串文字的Unicode代码点的序列。
Java编程语言要求相同的字符串文字(即,包含相同代码点序列的文字)必须引用String类的相同实例(JLS§3.10.5)。另外,如果在任何字符串上调用String.intern方法,则结果是对该类实例的引用,如果该字符串以文字形式出现,则将返回该实例。因此,以下表达式必须具有值true:
("a" + "b" + "c").intern() == "abc"
为了派生字符串文字,Java虚拟机检查由CONSTANT_String_info结构给出的代码点序列。
如果先前在类String的实例上调用了String.intern方法,该实例包含与CONSTANT_String_info结构给出的序列相同的Unicode代码点序列,则字符串文字派生的结果是对该类String的该实例的引用。
否则,将创建一个新的String类实例,其中包含由CONSTANT_String_info结构给出的Unicode代码点序列;对该类实例的引用是字符串文字派生的结果。最后,调用新的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
:创建了一个新的字符串实例(#2
作为参数)35
:a
和c
作为常规对象进行比较if_acmpne
在字节码上,常量字符串的表示非常神奇:
new String
并且上面的JVMS引用似乎表明,只要Utf8指向相同,就会通过加载相同的实例ldc
。
我已经对字段进行了类似的测试,并且:
static final String s = "abc"
通过ConstantValue属性指向常量表ldc
奖励:将其与没有直接字节码支持(即无模拟)的Integer池进行比较CONSTANT_String_info
。
我只会在字符串的多次比较中使equals-comparison成为瓶颈的情况下,检查intern和==-comparison而不是equals。因为intern()不是免费的,所以这极不可能帮助进行少量的比较。在积极地实习字符串之后,您会发现对intern()的调用越来越慢。
subString()
与源字符串相比,结果的使用量较小且对象的寿命较长时,可能会导致使用某种内存泄漏。
正常的解决方案是使用,new String( s.subString(...))
但是当您有一个存储潜在/可能结果的类subString(...)
并且无法控制调用者时,您可能会考虑存储intern()
传递给构造函数的String参数。这释放了潜在的大缓冲区。
在equals()
频繁调用该方法的情况下,字符串插入很有用,因为该equals()
方法会快速检查以查看对象在方法开始时是否相同。
if (this == anObject) {
return true;
}
当搜索Collection
其他代码也可能进行字符串相等性检查时,通常会发生这种情况。
但是,进行实习需要一定的成本,我对一些代码进行了微基准测试,发现实习过程使运行时间增加了10倍。
进行实习的最佳位置通常是在读取存储在代码外部的键时,因为代码中的字符串会被自动实习。通常,这会在应用程序的初始化阶段发生,以防止第一用户的损失。
可以完成此操作的另一个地方是在处理可用于进行键查找的用户输入时。这通常发生在您的请求处理器中,请注意,应该将传入的字符串向下传递。
除此之外,对代码的其余部分进行实习没有多大意义,因为它通常不会带来任何好处。
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
它比较字符串的长度,然后比较内容。
(顺便说一句,销售目录中的产品代码字符串可能具有相同的长度-BIC0417是骑自行车的人的安全帽,TIG0003是活的成年雄性老虎-您可能需要各种许可证才能订购其中的一个。也许您最好同时订购安全帽。)
因此,听起来好像您可以从用Strings intern()
版本替换Strings中受益,但是无需equals()
在编程中使用“ ==”即可获得安全性-可读性和标准合规性- 。我要说的大部分内容都取决于事实是否正确。
但是String.equals()
在使用前是否测试过是否将字符串而不是其他对象传递给了它"=="
?我没有资格说,但我不会猜到,因为绝大多数此类equals()
操作大多数都是从String到String,因此几乎总是通过测试。的确,在内部String.equals()
对“ ==”设置优先级意味着您可以经常将String与相同的实际对象进行比较。
我希望没人对以下几行产生“ false”的结果感到惊讶:
Integer i = 1;
System.out.println("1".equals(i));
但是,如果更改i
为i.toString()
第二行,那当然是true
。
您可能希望从实习中受益的场所包括Set
和Map
。我希望对实习生字符串的哈希码进行缓存...我认为这是必要条件。而且我希望我不仅放弃了一个可以赚到我一百万美元的想法。:-)
对于内存,很明显,如果String的数量很大,或者您希望程序代码使用的内存很小,则这是一个重要的限制。如果-distinct-字符串的数量非常大,那么可能是时候考虑使用专用的数据库程序代码来管理它们以及单独的数据库服务器了。同样,如果您可以改进小型程序(需要同时在10000个实例中运行),则可以使其根本不存储其字符串。
创建一个新的String并立即丢弃以intern()
替换它是浪费时间,但是除了保留重复的String之外,没有其他明确的选择。因此,实际上执行成本是在内部池中搜索您的字符串,然后允许垃圾回收器处置原始字符串。而且,如果它是字符串文字,那么无论如何它都已经被内联了。
我想知道是否intern()
可以被恶意程序代码滥用,以检测某些String及其对象引用是否已在intern()
池中存在,因此是否应该在Java会话的其他位置存在(不应该知道)。我猜这只有在程序代码已经以信任的方式使用时才有可能。尽管如此,您仍然需要考虑程序中所包含的第三方库来存储和记住您的ATM PIN码!
使用实习生的真正原因并非以上所述。您遇到内存不足错误后就可以使用它。在典型程序中,很多字符串是其他大字符串的String.substring()[考虑从100K xml文件中提取用户名。Java实现是,子字符串包含对原始字符串的引用以及该巨大字符串中的start + end。(其背后的思想是重用相同的大字符串)
在1000个大文件之后,您仅会保存1000个短名称,您将在内存中保留整个1000个文件!解决方案:在这种情况下,只需使用smallsubstring.intern()
让我们面对现实:主要的用例场景是当您读取数据流(通过输入流或从JDBC ResultSet中读取)时,有无数的小字符串重复出现。
这是一个小技巧,使您可以控制要用于内部化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();
}
}
当我从流或ResultSet中读取字段时,我经常使用它。注意:LRUCache
是基于的简单缓存LinkedHashMap<K,V>
。retrieve()
对于所有的缓存未命中,它将自动调用用户提供的方法。
使用此方法的方法是LRUInternalizer
在读取之前创建一个,然后使用它内部化Strings和其他小的不可变对象,然后释放它。例如:
Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
s = internalizer.get(s);
// store s...
}
我正在使用它来缓存链接到相关名称的大约36000个代码的内容。我在缓存中实习字符串,因为许多代码都指向相同的字符串。
通过在缓存中插入字符串,我可以确保指向同一字符串的代码实际上指向同一内存,从而节省了我的RAM空间。
如果实习生的弦实际上是垃圾收集的,那对我来说根本不起作用。这将基本上否定实习的目的。我不会被垃圾收集,因为我持有对缓存中每个字符串的引用。
如果您正在寻找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;
}
}
当然,如果可以大致估计会有多少个不同的字符串,则只需将String.intern()与-XX:StringTableSize = highEnoughValue一起使用。