使用java.lang.String.intern()是一种好习惯吗?

Dan*_*ski 193 java string

Javadoc关于String.intern()没有提供太多细节.(简而言之:它返回字符串的规范表示,允许使用内部字符串进行比较==)

  • 我何时会使用此功能String.equals()
  • 是否存在Javadoc中未提及的副作用,即JIT编译器或多或少的优化?
  • 有进一步的用途String.intern()吗?

Dan*_*ner 193

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

  • 需要澄清 - 实习总是自动发生在编译时常量字符串(文字和固定表达式).另外,在运行时动态评估字符串时调用String.intern()时会发生这种情况. (15认同)
  • 那是不对的.在评估每个字符串表达式时,字符串的实际内容始终会自动发生.对于每个使用的唯一字符串,始终有一个副本,如果出现多个用法,则"内部共享".调用String.intern()不会使这一切发生 - 它只返回内部规范表示.见javadoc. (4认同)

dfa*_*dfa 125

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

当你需要速度,因为你可以通过引用比较字符串(==快于等于)

是否有Javadoc中没有提到的副作用?

主要的缺点是你必须记住确保你实际上做了你要比较的所有字符串intern().很容易忘记intern()所有字符串,然后你可以得到令人困惑的错误结果.此外,为了每个人的利益,请务必非常清楚地记录您依赖于内部化的字符串.

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

(来自JGuru)

第三个缺点(仅限Java 7或更低版​​本):实习字符串存在于PermGen空间中,通常非常小; 您可能遇到具有大量可用堆空间的OutOfMemoryError.

(来自Michael Borgwardt)

  • 第三个缺点:实习字符串存在于PermGen空间,通常很小; 您可能遇到具有大量可用堆空间的OutOfMemoryError. (64认同)
  • 实习生是关于内存管理,而不是比较速度.除非你有很多具有相同前导字符的长字符串,否则`if(s1.equals(s2))`和`if(i1 == i2)`之间的区别是最小的.在大多数实际使用中(URL除外),字符串在前几个字符中会有所不同.而且if if-else链也是代码气味:使用枚举和仿函数贴图. (31认同)
  • 你仍然可以在整个程序中使用s1.equals语法,不要使用==,.equals在内部使用==进行短路评估 (25认同)
  • AFAIK更新的VM也垃圾收集PermGen空间. (15认同)
  • Michael Borgwardt没有说实习字符串不能被垃圾收集.这是一个假的断言.迈克尔的评论(正确地)说的比这更微妙. (15认同)
  • 在Java 1.7中实时插入堆中的String http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931 (6认同)
  • 嘿,请解释一下"实习生"必须首次计算字符串的哈希码,因此它必须迭代所有字符串,这比简单比较更昂贵(比较可以在所有字符串迭代之前结束).所以**实习生只有比较多次相同的字符串**才有意义.另外,它似乎不是一个好的编程实践,因为可能有更好的方法来改善性能而不是以这种方式降级代码(对于我放弃像*equals*这样的抽象解决方案正在降级). (4认同)
  • 在JDK 7中,实现的字符串不再分配在Java堆的永久生成中,而是分配在Java堆的主要部分(称为年轻和旧的代)中,以及应用程序创建的其他对象.参考:http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html (2认同)
  • `当你需要速度,因为你可以通过引用比较字符串(==比等于更快)`.我可能错了,但第一件事[`equals`确实如此](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String. java#String.equals%28java.lang.Object%29)正在检查引用是否相同.没有方法调用是否有预期的速度差异? (2认同)

Gil*_*hum 36

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

非GCed String.intern()神话中看到更多(来自我).

  • `OutOfMemoryException` - 不,不是上面的代码,在我的*大脑*:链接到javaturning文章,指向这篇文章,指向javaturning文章,其中...... :-) (26认同)
  • @Carlos链接一个链接回stackoverflow的外部引用应该导致.. Stackoverflow :) (11认同)
  • 您可能想要提及您也是作者的链接外部参考. (3认同)
  • @Seiti这些天很容易发现循环引用:p (2认同)

小智 16

我最近在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(默认值可能太小;设置一个素数)

  • 感谢您写@ mik1!非常翔实,清晰和最新的文章.(我回到这里打算自己发布一个链接.) (3认同)
  • 请不要只发布指向您博客的链接,这被某些人视为垃圾邮件.加上博客链接有一个明显的趋势,死亡404死亡.请在此处内联汇总您的文章,或在评论中留下该链接. (2认同)

ale*_*oot 13

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

5时间更快,但由于字符串比较通常仅占应用程序总执行时间的一小部分,因此整体增益远小于此,并且最终收益将被稀释到几个百分点.

String.intern()将字符串拉离Heap并将其放入PermGen

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

String.intern()字符串是垃圾回收

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

记住上面的3点,你可以推断出String intern()只有在很少的情况下你做了很多字符串比较才有用,但如果你不确切知道你是什么,最好不要使用内部字符串是做 ...

  • 从[Java 7中,实习字符串在堆中](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931). (4认同)

Pet*_*ham 7

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

鉴于他们做不同的事情,可能永远不会.

出于性能原因的内部字符串,以便您可以比较它们以获得引用相等性,只有在持有对字符串的引用一段时间才会有好处 - 来自用户输入的字符串或IO将不会被实现.

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

创建一个内部UserId类型(很容易创建一个线程安全的通用实习机制)并且像开放枚举一样,几乎总是更好,而不是java.lang.String如果恰好是用户ID那么用引用语义重载类型.

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


obj*_*cts 6

我不知道有任何优点,如果有人认为equals()本身会在内部使用intern()(它没有).

B实习生()神话

  • 尽管您说您没有意识到任何优势,但您发布的链接标识通过==进行比较,因为速度提高了5倍,因此对于以文本为中心的高性能代码非常重要 (7认同)
  • 如果你有很多文本 - 比较你最终将耗尽PermGen空间.当没有那么多文本时 - 速度差异无关紧要.无论哪种方式,只是不要实习()你的字符串.这不值得. (3认同)

Cir*_*四事件 5

\n

是否存在 Javadoc 中未提及的副作用,即 JIT 编译器或多或少的优化?

\n
\n\n

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

\n\n

JVMS

\n\n

JVMS 7 5.1 说

\n\n
\n

字符串文字是对 String 类实例的引用,派生自类或接口的二进制表示形式中的 CONSTANT_String_info 结构 (\xc2\xa74.4.3)。CONSTANT_String_info 结构给出了构成字符串文字的 Unicode 代码点序列。

\n\n

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

\n
\n\n
("a" + "b" + "c").intern() == "abc"\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

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

\n\n
    \n
  • 如果先前已在包含与 CONSTANT_String_info 结构给出的序列相同的 Unicode 代码点序列的 String 类实例上调用了 String.intern 方法,则字符串文字派生的结果是对 String 类的同一实例的引用。

  • \n
  • 否则,将创建 String 类的新实例,其中包含 CONSTANT_String_info 结构给出的 Unicode 代码点序列;对该类实例的引用是字符串文字派生的结果。最后,调用新String实例的intern方法。

  • \n
\n
\n\n

字节码

\n\n

查看 OpenJDK 7 上的字节码实现也很有启发性。

\n\n

如果我们反编译:

\n\n
public class StringPool {\n    public static void main(String[] args) {\n        String a = "abc";\n        String b = "abc";\n        String c = new String("abc");\n        System.out.println(a);\n        System.out.println(b);\n        System.out.println(a == c);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们在常量池上有:

\n\n
#2 = String             #32   // abc\n[...]\n#32 = Utf8               abc\n
Run Code Online (Sandbox Code Playgroud)\n\n

main

\n\n
 0: ldc           #2          // String abc\n 2: astore_1\n 3: ldc           #2          // String abc\n 5: astore_2\n 6: new           #3          // class java/lang/String\n 9: dup\n10: ldc           #2          // String abc\n12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V\n15: astore_3\n16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;\n19: aload_1\n20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;\n26: aload_2\n27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;\n33: aload_1\n34: aload_3\n35: if_acmpne     42\n38: iconst_1\n39: goto          43\n42: iconst_0\n43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意如何:

\n\n
    \n
  • 0and 3ldc #2加载相同的常量(文字)
  • \n
  • 12:创建一个新的字符串实例(作为#2参数)
  • \n
  • 35:ac作为常规对象进行比较if_acmpne
  • \n
\n\n

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

\n\n\n\n

上面的 JVMS 引用似乎是说,每当 Utf8 指向相同时,就会加载相同的实例ldc

\n\n

我对字段做了类似的测试,并且:

\n\n
    \n
  • static final String s = "abc"通过ConstantValue 属性指向常量表
  • \n
  • 非最终字段没有该属性,但仍然可以用ldc
  • \n
\n\n

好处:将其与整数池进行比较,后者没有直接的字节码支持(即没有CONSTANT_String_info类似物)。

\n