使用Java.Lang.String.intern()是一种好的做法吗?

关于String.intern()的Javadoc没有给出太多细节。(简而言之:它返回字符串的规范表示,允许使用==来比较嵌套字符串)

  • 在什么情况下,我可以使用此功能来String.equals()
  • 是否存在Javadoc中未提及的副作用,即JIT编译器的优化程度更高或更低?
  • String.intern()还有其他用途吗?
64726 次浏览

什么时候我会使用这个函数来支持String.equals()?

当您需要速度时,因为您可以通过引用来比较字符串(==比=更快)

是否存在Javadoc中未提及的副作用?

主要的缺点是,您必须记住确保您确实对要比较的所有字符串执行了intern()。很容易忘记对所有字符串执行intern(),然后您可能会得到令人困惑的错误结果。此外,为了每个人的利益,请确保非常清楚地记录您正在依赖被内部化的字符串。

如果您决定将字符串内部化,第二个缺点是intern()方法的开销相对较大。它必须管理唯一字符串的池,所以它做了相当多的工作(即使字符串已经被内部化)。所以,在你的代码设计中要小心,这样你就可以在输入中输入所有适当的字符串,这样你就不用再担心了。

(来自JGuru)

第三个缺点(仅限Java7或更低版本):嵌套字符串位于Permgen空间中,该空间通常非常小。您可能会遇到有大量可用堆空间的OutOfMemoryError错误。

(来自Michael Borgwardt)

我不知道有什么好处,如果有的话,我会认为equals()本身会在内部使用intern()(它没有)。

打破实习生()神话

这(几乎)与字符串比较无关。如果应用程序中有许多具有相同内容的字符串,则串实习旨在节省内存。通过使用String.intern(),从长远来看,应用程序将只有一个实例,其副作用是您可以执行快速引用相等比较,而不是普通的字符串比较(但这通常是不可取的,因为它很容易因忘记只插入一个实例而中断)。

只有在字符串的多重比较中,equals-comparison是瓶颈的情况下,我才会检查intern和==-comparison而不是equals.这不太可能对少量的比较有帮助,因为intern()不是免费的。在积极地调用字符串之后,您会发现对intern()的调用变得越来越慢。

什么时候我会使用这个函数来支持String.equals()?

考虑到他们做不同的事情,可能永远不会。

出于性能原因而插入字符串,以便您可以比较它们的引用是否相等,只有在您将对字符串的引用保留一段时间时才会有好处-来自用户输入或IO的字符串不会被插入。

这意味着在您的应用程序中,您从外部源接收输入,并将其处理为具有语义值(例如标识符)的对象,但该对象具有与原始数据无法区分的类型,并且对于程序员应该如何使用它具有不同的规则。

创建一个UserId类型,它是Interned的(很容易创建一个线程安全的通用Interning机制),并且像一个开放枚举一样工作,这几乎总是比用引用语义重载java.lang.String类型(如果它碰巧是一个用户ID)要好。

这样,您就不会混淆特定字符串是否已被插入,并且您可以在开放枚举中封装所需的任何其他行为。

我会投票给它,因为它不值得维护。

大多数时候,没有必要,也没有性能优势,除非你的代码做了很多关于子字符串的工作。在这种情况下,String类将使用原始字符串加上偏移量来节省内存。如果您的代码大量使用子字符串,那么我怀疑这只会导致您的内存需求激增。

_,在现代JVM中,ABC_0肯定是垃圾收集。
由于GC活动,以下内容永远不会耗尽内存:

// 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();
}
}
}

在_ABC_上查看更多内容(通过我)0。

使用Intern

的真正原因不是上述原因。 在出现内存不足错误后,您可以使用它。典型程序中的许多字符串都是其他大字符串的String.substring()[考虑从100K的XML文件中取出用户名。 Java的实现是,子字符串保存对原始字符串的引用,以及该巨大字符串中的开始+结束。(背后的思想是同一大串的再利用)

1000个大文件之后,您只保存了1000个短名称,您将在内存中保存整个1000个文件! 解决方案:在这种情况下,只需使用smallSubstring.intern()

我使用Intern来节省内存,我在内存中保存了大量的字符串数据,使用Intern()节省了大量的内存。不幸的是,尽管它使用的内存很少,但它使用的内存存储在Permgen内存中,而不是堆中,很难向客户解释如何增加这种类型的内存的分配。

那么,有没有一种替代intern()的方法来减少内存消耗(==vs.equals性能优势对我来说不是问题)?

使用==比较字符串比使用equals()快得多。

5倍,但由于字符串比较通常只占应用程序总执行时间的一小部分,因此整体增益要比这小得多,最终增益将被稀释到百分之几。

String.intern()将字符串从堆中拉出,并将其放入Permgen中

内部化的字符串被放在不同的存储区域:永久生成,这是JVM中为非用户对象(如类、方法和其他内部JVM对象)保留的区域。这个区域的大小是有限的,并且比堆要珍贵得多。由于该区域小于堆,因此使用所有空间并获得OutOfMemoryException的可能性更大。

String.intern()字符串被垃圾收集

在JVM的新版本中,当没有任何对象引用时,内部化的字符串也会被垃圾收集。

记住上面的3点,你可以推断,当你做大量的字符串比较时,字符串intern()只在少数情况下有用,但是如果你不知道你到底在做什么,最好不要使用内部字符串。

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是活的成年雄性老虎- 你可能需要各种各样的许可证来订购其中的一个。也许你最好同时订购一顶安全帽。

因此,这听起来像是将字符串替换为其intern()版本会带来好处,但您可以获得安全性、可读性和标准遵从性,而无需在编程中使用“==”作为equals()。我要说的大部分都取决于这是真的,如果它是真的。

但是,在使用"=="之前,String.equals()是否测试您传递给它的是字符串而不是其他对象?我没有资格说,但我猜不会,因为绝大多数这样的equals()操作将是字符串到字符串,所以测试几乎总是通过。实际上,在String.equals()中设置“==”的优先级意味着您经常将字符串与相同的实际对象进行比较。

我希望没有人会对下面几行产生“ false ”的结果感到惊讶:

    Integer i = 1;
System.out.println("1".equals(i));

但是如果你在第二行把i改成i.toString(),当然它是true

显然,你可能希望从实习中受益的地方包括SetMap。我希望被托管的字符串的散列码被缓存..我认为这是一项要求。我希望我没有放弃一个可以让我赚到一百万美元的想法。

至于内存,同样明显的是,如果字符串的数量很大,或者如果您希望程序代码使用的内存非常小,那么这是一个重要的限制。如果您的-distinct-字符串量非常大,那么可能需要考虑使用专用的数据库程序代码和单独的数据库服务器来管理它们。同样,如果您可以改进一个小程序(需要同时运行10000个实例),让它完全不存储字符串本身。

创建一个新的字符串,然后直接将其丢弃,因为其intern()替代,这感觉很浪费,但除了保留重复的字符串外,没有明确的替代方法。因此,真正的执行成本是在Intern池中搜索您的字符串,然后允许垃圾收集器处理原始字符串。如果它是一个字符串文字,那么它已经被注册了。

我想知道intern()是否可以被恶意程序代码滥用,以检测某些字符串及其对象引用是否已经存在于intern()池中,并因此存在于Java会话中的其他地方,而这是不应该知道的。但我想,只有当程序代码已经以信任的方式被使用时,这才有可能。不过,在你的程序中包含第三方库来存储和记住你的ATM密码,这是需要考虑的事情。

如果与源字符串相比,结果很小,并且对象具有很长的生命周期,则使用subString()可能会产生一种内存泄漏。

通常的解决方案是使用new String( s.subString(...)),但是当您拥有一个存储潜在/可能的subString(...)的结果的类并且无法控制调用方时,您可以考虑存储传递给构造函数的字符串参数的intern()。这释放了潜在的大缓冲区。

让我们面对它:主要的用例场景是当您读取数据流(通过输入流或从JDBC ResultSet)时,有无数的小字符串在整个过程中重复。

下面是一个小技巧,它可以让您在某种程度上控制您想要使用哪种机制来内部化字符串和其他不可变项,以及一个示例实现:

/**
* 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();
}
}
当我从流或结果集中读取字段时,

我经常使用它。 注:LRUCache是基于LinkedHashMap<K,V>的简单缓存。对于所有缓存未命中,它会自动调用用户提供的retrieve()方法。

使用它的方法是在read(或reads)之前创建一个LRUInternalizer,使用它来内部化字符串和其他小的不可变对象,然后释放它。例如:

Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
s = internalizer.get(s);
// store s...
}

Daniel Brückner是绝对正确的,字符串插入是为了节省内存(堆)。,我们的系统目前有一个巨大的HashMap来保存某些数据。随着系统的扩展,HashMap将变得足够大,足以使堆超出内存(正如我们所测试的)。通过将所有重复的字符串插入到HashMap中的所有对象中,可以节省大量的堆空间。

此外,在Java7中,托管字符串不再存在于Permgen中,而是存在于堆中。,所以您不必担心它的大小,是的,它会被垃圾收集:

在JDK 7中,暂留字符串不再分配在永久 Java堆的生成,而是在主 Java堆的一部分(称为年轻一代和年老一代),以及 与应用程序创建的其他对象一起使用。这一变化将 导致更多的数据驻留在主Java堆中,而更少的数据驻留在 永久生成,因此可能需要堆大小为 已调整。大多数应用程序只能看到相对较小的差异 由于这一变化,在堆使用中,但较大的应用程序加载 许多类或大量使用String.intern()方法将看到 更显著的差异.

我使用它是为了缓存链接到相关名称的大约36000个代码的内容。我在缓存中插入字符串,因为许多代码都指向同一个字符串。

通过在缓存中插入字符串,我可以确保指向相同字符串的代码实际上指向相同的内存,从而节省RAM空间。

如果插入的字符串实际上是垃圾收集,它根本不会为我工作。这将从根本上否定实习的目的。我的不会被垃圾收集,因为我在缓存中保存了对每个字符串的引用。

我最近写了一篇关于Java 6、7和8中String.intern()实现的文章: String.Intern in Java 6,7 and 8-字符串池.

我希望它应该包含足够的关于Java中字符串池的当前情况的信息。

简而言之:

  • 在Java6中避免String.intern(),因为它进入Permgen
  • 在Java 7中首选String.intern()&;Java8:它使用的内存比滚动您自己的对象池少4-5倍
  • 确保将_ABC调整为_0(默认值可能太小;设置质数)

在一个字符串中实习的成本比在单个字符串中节省的时间要多得多。仅当您重复使用相同的未更改字符串变量时,才使用它(出于性能原因)。例如,如果您定期迭代一个稳定的字符串列表,以更新同一字符串字段上的一些映射,您可以获得很好的节省。

我建议您在优化代码的特定部分时,使用String Interning来调整性能。

还要记住,字符串是不可变,不要犯

String a = SOME_RANDOM_VALUE
a.intern()

记得做

String a = SOME_RANDOM_VALUE.intern()

在经常调用equals()方法的情况下,字符串插入非常有用,因为equals()方法在方法开始时会快速检查对象是否相同。

if (this == anObject) {
return true;
}

这通常发生在搜索Collection时,尽管其他代码也可能执行字符串相等性检查。

虽然实习是有成本的,但我对一些代码进行了微基准测试,发现实习过程将运行时间增加了10倍。

当代码中的字符串被自动插入时,读取存储在代码之外的密钥通常是进行插入的最佳位置。这通常发生在应用程序的初始化阶段,以防止第一个用户受到惩罚。

另一个可以这样做的地方是在处理可用于进行键查找的用户输入时。这通常发生在您的请求处理器中,请注意,插入的字符串应该向下传递。

除此之外,在代码的其余部分进行实习没有多大意义,因为它通常不会带来任何好处。

是否存在Javadoc中未提及的副作用,即JIT编译器的优化程度更高或更低?

我不了解JIT级别,但字符串池有直接的字节码支持。,它是通过专用的CONSTANT_String_info结构神奇而高效地实现的(不像大多数其他对象具有更多的通用表示)。

JVM

JVMS 75.1表示

字符串文字是对类String的实例的引用,并且是从类或接口的二进制表示中的常量_字符串_信息结构(§4.4.3)派生的。常量_字符串_信息结构给出了组成字符串文字的Unicode码位序列。

Java编程语言要求相同的字符串文字(即包含相同代码点序列的文字)必须引用类String的相同实例(JLS§3.1 0.5)。此外,如果对任何字符串调用String.Intern方法,则结果是对相同类实例的引用,如果该字符串显示为文字,则将返回该实例。因此,以下表达式的值必须为true:

("a" + "b" + "c").intern() == "abc"

为了派生字符串文字,Java虚拟机检查常量_字符串_信息结构给出的代码点序列。

  • 如果以前在类String的实例上调用过String.intern方法,该实例包含的Unicode代码点序列与常量_字符串_信息结构给出的代码点序列相同,则字符串文字派生的结果是对类String的同一实例的引用。

  • 否则,将创建一个新的String类实例,其中包含常量_字符串_信息结构给出的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

注意方式:

  • 03:加载相同的ldc #2常量(文字)
  • 12:创建了新的字符串实例(使用#2作为参数)
  • 35ac作为常规对象与if_acmpne进行比较

常量字符串的表示在字节码上非常神奇:

  • 与常规对象(例如,new String)不同,它具有专用的常量_字符串_信息结构。
  • 该结构指向包含数据的ABC_0_。这是表示字符串的唯一必需数据。

上面引用的JVM似乎是说,只要指向的UTF8相同,ldc就会加载相同的实例。

我对田地做过类似的测试,并且:

  • static final String s = "abc"通过ConstantValue属性指向常数表
  • 非final字段没有该属性,但仍可以使用ldc进行初始化。

奖金:将其与整数池进行比较,后者不具有直接字节码支持(即,没有CONSTANT_String_info模拟)。

如果你正在寻找一个无限的替代字符串。实习生,也是垃圾收集,以下是我工作得很好。

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=足够高的价值