【问题标题】:String Pool behavior字符串池行为
【发布时间】:2013-01-07 13:00:27
【问题描述】:

我读到了这个Questions about Java's String pool 并了解字符串池的基本概念但仍然不了解行为。

首先:如果您直接分配值并且 s1 和 s2 都引用池中的同一个对象,则它可以工作

String s1 = "a" + "bc";
String s2 = "ab" + "c";
System.out.println("s1 == s2? " + (s1 == s2));

但是如果我更改字符串 s1+="d",那么池应该有一个字符串对象“abcd”?那么当我更改 s2+="d" 时,它应该在池中找到字符串对象“abcd”并将该对象分配给 s2?但事实并非如此,它们也没有引用同一个对象。这是为什么呢?

String s1 = "abc";
String s2 = "abc";
System.out.println("s1 == s2? " + (s1 == s2));

s1 += "d";                  
s2 += "d";
System.out.println("s1 == s2? " + (s1 == s2));

【问题讨论】:

标签: java string pool


【解决方案1】:

当您在字符串上调用String.intern() 时,保证会合并字符串。

String s1 = "abcd".intern();
String s2 = "abc";
s2 += "d";
s2 = s2.intern();
s1 == s2 // returns true

当编译器看到一个常量时,它足够聪明地优化和池化字符串文字,即:

String s1 = "abcd";
String s2 = "abcd";
s1 == s2 // returns true

Java Language Specification 状态:

每个字符串字面量都是一个引用 (§4.3) 到一个实例 (§4.3.1, §12.5) 类字符串(§4.3.3)。细绳 对象具有恒定值。细绳 文字,或者更一般地说,字符串 这是常量的值 表达式(第 15.28 节)- 被“实习”所以 至于共享独特的实例,使用 String.intern 方法。

所以在s2 += "d" 的情况下,编译器没有你那么聪明,只是汇集了"d"

【讨论】:

  • 虽然这个答案陈述了一个真实的事实,但它并没有解释问题的为什么基础。
  • 很有趣,那么整个字符串池的事情是否漏掉了重点?如果我需要将 .intern() 添加到每个字符串?
  • @user2005443 通常不应将intern() 添加到每个字符串中。那是过早的优化。如果您知道(或通过分析发现)某些字符串将被大量使用,它可以帮助提高性能,但如果您不加选择地使用它,它可以be quite dangerous
【解决方案2】:

对此我不确定,所以这只是猜测,但我怀疑第一个示例中可能存在一些编译器诡计(它是内联的并且很明显发生了什么),但这并不聪明足以在第二个示例中完成它(它不是那么明显)。

如果我是对的,要么编译器看到"a" + "bc" 并在编译时简单地将其压缩为"abc",要么它看到这两行并合并字符串,因为它意识到它们将被使用。我赌的是前者..

并非所有字符串都必须被合并。

【讨论】:

  • 没错,编译器对编译时常量进行常量折叠,并且只将一个字符串“abc”放入类的常量池中。我相信来自常量池的所有字符串都被保留。其他在运行时创建的字符串不会被实习,除非你明确地对它们调用 String#intern。
  • 确实是这样。如果您检查字节码,您会看到“a”+“bc”被转换为单个 ldc 指令(推送一个常量),而另一条指令 s1 +“d”被转换为对 StringBuilder 的调用。
【解决方案3】:

请参阅String#intern() 的文档。那里的最后一行声明:

所有文字字符串和字符串值常量表达式都是 实习。

您的+= 示例既不是文字字符串也不是字符串值的常量表达式,因此它不会放入字符串池中。

【讨论】:

    【解决方案4】:

    编译器可以执行常量评估,但不能在您修改值的情况下执行

    尝试跟随,看看如果从任一变量中删除 final 会发生什么。

    final String s1 = "abc";
    final String s2 = "abc";
    System.out.println("s1 == s2? " + (s1 == s2));
    
    String s3 = s1 + "d";                  
    String s4 = s2 + "d";
    System.out.println("s3 == s4? " + (s3 == s4));
    

    【讨论】:

      【解决方案5】:

      这是我的猜测:

      字符串 s1 = "a" + "bc"; 字符串 s2 = "ab" + "c";

      我认为这是编译时,它们被确定为生成相同的字符串,因此只为两者创建一个对象。

      但是当你给它们都加上“d”时,这两个字符串是分开完成的(因为它是在实时完成的,可能会有异常中断它等,所以它不能预先做)所以它不会自动让它们引用一个对象。

      【讨论】:

      • 为什么要猜?这一切都在 JLS 中进行了描述。
      【解决方案6】:

      我认为这里发生的是: 1.对于 字符串 s1 = "a" + "bc"; 字符串 s2 = "ab" + "c"; Java 编译器足够聪明,知道 s1 和 s2 的字面值是相同的,所以编译器将它们指向字符串池中相同的字面值

      1. 为 s1 += "d";
        s2 += "d";

      编译器无法知道 s1 和 s2 是否最终会是相同的值, 在运行时,除非您调用 String.intern(),否则 jvm 不会检查字符串文字池以查看该值是否已经存在。

      【讨论】:

        猜你喜欢
        • 2013-10-25
        • 1970-01-01
        • 1970-01-01
        • 2010-12-26
        • 2014-06-08
        • 1970-01-01
        • 2018-06-21
        • 1970-01-01
        • 2012-12-18
        相关资源
        最近更新 更多