【问题标题】:What is String pool in Java? [duplicate]Java中的字符串池是什么? [复制]
【发布时间】:2011-04-17 14:40:57
【问题描述】:

我对 Java 中的 StringPool 感到困惑。我在阅读 Java 中的 String 章节时遇到了这个问题。请用外行的方式帮助我理解 StringPool 的实际作用。

【问题讨论】:

    标签: java


    【解决方案1】:

    这将打印true(即使我们不使用equals 方法:比较字符串的正确方法)

        String s = "a" + "bc";
        String t = "ab" + "c";
        System.out.println(s == t);
    

    当编译器优化你的字符串字面量时,它发现st 具有相同的值,因此你只需要一个字符串对象。它是安全的,因为 String 在 Java 中是不可变的。
    结果,st 都指向同一个对象,并节省了一些内存。

    命名“字符串池”源于所有已定义的字符串都存储在某个“池”中,并且在创建新的String 对象之前编译器会检查此类字符串是否已定义。

    【讨论】:

    【解决方案2】:

    我不认为它实际上做了很多,它看起来只是一个字符串文字的缓存。如果您有多个值相同的字符串,则它们都将指向字符串池中的相同字符串文字。

    String s1 = "Arul"; //case 1 
    String s2 = "Arul"; //case 2 
    

    在情况 1 中,新创建的文字 s1 并保存在池中。但在情况 2 中,文字 s2 引用 s1,它不会创建新的。

    if(s1 == s2) System.out.println("equal"); //Prints equal. 
    
    String n1 = new String("Arul"); 
    String n2 = new String("Arul"); 
    if(n1 == n2) System.out.println("equal"); //No output.  
    

    http://p2p.wrox.com/java-espanol/29312-string-pooling.html

    【讨论】:

      【解决方案3】:

      当 JVM 加载类或以其他方式看到文字字符串或某些代码 interns 字符串时,它会将字符串添加到一个几乎隐藏的查找表中,该查找表中每个此类字符串都有一个副本。如果添加了另一个副本,运行时会对其进行排列,以便所有文字都引用同一个字符串对象。这被称为“实习”。如果你说类似

      String s = "test";
      return (s == "test");
      

      它会返回true,因为第一个和第二个“测试”实际上是同一个对象。以这种方式比较实习字符串可能比String.equals 快​​得多,很多,因为只有一个引用比较,而不是一堆 char 比较。

      您可以通过调用String.intern() 将字符串添加到池中,这将返回字符串的池化版本(这可能与您正在实习的字符串相同,但如果依赖它会很疯狂——你经常不能确定到底什么代码已经加载并运行到现在并实习了相同的字符串)。池化版本(从intern 返回的字符串)将等于任何相同的文字。例如:

      String s1 = "test";
      String s2 = new String("test");  // "new String" guarantees a different object
      
      System.out.println(s1 == s2);  // should print "false"
      
      s2 = s2.intern();
      System.out.println(s1 == s2);  // should print "true"
      

      【讨论】:

      • 我实际上不认为它是在运行时完成的。即使是用方法构造的最简单的字符串也不会被池化。例如,如果我使用 concat 而不是 +,我的回答中的示例将不起作用
      • @Nikita:那是因为concat 不能那么容易地被优化掉。与+ 组合在一起的字符串可能会被任何自尊的编译器预先组合,因为值永远不会改变。但是编译器无法真正猜测一个函数是否会一直返回相同的值(有些不会),所以它不会尝试。如果您在示例中使用concat,则“ab”、“c”、“a”和“bc”将被实习,但“abc”不会(因为它不是文字,而且您的代码没有tintern它)。然而,使用+,一个不错的编译器会看到两个字符串都是“abc”并编译它。
      • 实习将必须在运行时完成,因为 (1) 池总是一开始是空的,(2) 两个不同的类可能每个都有“abc”他们。如果实习是编译时的事情并且两个类最终都被加载了,那么字符串池中最终会有两个“abc”,这违背了字符串池的全部目的。
      【解决方案4】:

      让我们从虚拟机规范中的一段引述开始:

      加载包含字符串文字的类或接口可能会创建一个新的字符串对象(第 2.4.8 节)来表示该文字。如果已经创建了一个 String 对象来表示该文字的先前出现,或者如果在表示与文字相同的字符串的 String 对象上调用了 String.intern 方法,则可能不会发生这种情况。

      这可能不会发生 - 这是一个提示,String 对象有一些特别之处。通常,调用构造函数会总是创建一个新的类实例。字符串不是这种情况,尤其是当使用文字“创建”字符串对象时。这些字符串存储在全局存储(池)中 - 或者至少引用保存在池中,并且每当需要已知字符串的新实例时,vm 从池中返回对对象的引用。在伪代码中,它可能是这样的:

      1: a := "one" 
         --> if(pool[hash("one")] == null)  // true
                 pool[hash("one") --> "one"]
             return pool[hash("one")]
      
      2: b := "one" 
        --> if(pool[hash("one")] == null)   // false, "one" already in pool
              pool[hash("one") --> "one"]
            return pool[hash("one")] 
      

      所以在这种情况下,变量ab 持有对同一个对象的引用。在这种情况下,我们有(a == b) && (a.equals(b)) == true

      如果我们使用构造函数,情况就不是这样了:

      1: a := "one"
      2: b := new String("one")
      

      同样,"one" 是在池中创建的,但随后我们从相同的文字创建了一个新实例,在这种情况下,它会导致 (a == b) && (a.equals(b)) == false

      那么为什么我们有一个字符串池?字符串,尤其是字符串文字在典型的 Java 代码中被广泛使用。而且它们是不可变的。并且不可变允许缓存 String 以节省内存并提高性能(创建工作量更少,收集的垃圾更少)。

      作为程序员,我们不必太在意字符串池,只要牢记在心:

      • (a == b) && (a.equals(b)) 可能是 truefalse总是使用 equals 比较字符串)
      • 不要使用反射来更改字符串的支持char[](因为您不知道谁在实际使用该字符串)

      【讨论】:

      • 如果您确实关心字符串池,那么在广泛使用一小组字符串(通常作为标记或关键字)的应用程序中,可能会大幅提升性能。一旦字符串被插入,比较就变成了一个==,而不是函数调用、两个length()调用,以及equals可能发生的一堆char比较。
      • @cHao 为了安全和一致性,您仍然可以将String.equals() 与实习字符串一起使用,因为String.equals() 首先进行== 比较
      • @bcoughlan: ==equals 一样安全和一致——只是被误解了。将它与对象一起使用的人通常分为两类。有些人不了解价值与身份语义(并且 == 与引用类型比较身份)——那些人应该总是使用String.equals。还有一些人确实了解,但有意识地选择身份。只要你知道你的对象来自哪里,这同样可靠。 == 与对象一起工作是有原因的——尤其是为什么它不只是调用 equals
      • @cHao 关键是“只要你知道你的对象来自哪里”。 if (s1==s2) 对大多数人来说看起来很可疑(并被 FindBugs 标记)。我只是指出,您仍然可以获得与字符串池进行比较的性能提升,而无需编写假定字符串被保留的代码
      • @bcoughlan:你可以获得一些的提升,但你仍然有一个方法调用。在my tests 中,该方法调用显着增加了函数的整体运行时间——比如+100%。这是一个旨在至少有点现实的测试。
      猜你喜欢
      • 2012-03-31
      • 2011-01-29
      • 2012-10-25
      • 1970-01-01
      • 2023-03-21
      • 2017-06-02
      • 2013-06-08
      • 1970-01-01
      相关资源
      最近更新 更多