【问题标题】:Search cost of string interning and declaration of literal strings字符串驻留的搜索成本和文字字符串的声明
【发布时间】:2011-07-01 00:03:44
【问题描述】:

两个问题。

  1. 当我们声明字面量字符串时,我们会在堆的字符串池中查找是否有相同的字符串。 这也是实习(String类的方法实习生)吗?

  2. 在我看来,每个文字字符串声明都需要二进制搜索或其他东西,因此当 n 是现有字符串的数量时,它至少花费 log(n)在游泳池。如果池中有很多字符串,则可能成本很高。 (也许是搜索成本和内存的权衡?)从这个角度来看,声明 mant 文字字符串可能很危险。 这种搜索成本有多重要以及为什么以这种方式设计 java(在声明文字字符串时搜索池)。

以下是我所说的了解背景。


JavaDoc for the java.lang.String class 声明:

字符串是常量;它们的值在创建后无法更改。字符串缓冲区支持可变字符串。因为 String 对象是不可变的,所以它们可以被共享。

http://www.janeg.ca/scjp/lang/strLiteral.htmlcmets:

换句话说,因为编译器知道字符串的原始值一旦创建就不能更改,它可以安全地使用现有数据并避免因重复而导致内存混乱。

【问题讨论】:

  • 我将您对“JSK 1.3”的引用修改为官方 JavaDoc。
  • @joachim Sauer 谢谢,但最后一句话来自您删除的 (janeg.ca/scjp/lang/strLiteral.html)。你能反映一下吗?或者我会的。
  • 我删除了它,因为我上面链接的 JavaDoc 是引用的权威原始来源,并且该页面的质量有问题(没有“JSK 1.3”之类的东西,它实际上并不链接到它的任何来源)。
  • @Joachim Sauer 哦.. 我知道,但我的意思是从 换句话说 开始的最后一个阶段。这句话可能是网站所有者(janeg.ca..)写的,所以我认为最好注意这一点。

标签: java performance search string-literals string-interning


【解决方案1】:

您将编译时间复杂性与运行时复杂性混淆了。

加载类时,是的,它会搜索每个文字是否已经存在(尽管我想它会使用哈希图进行 O(1) 查找而不是您的提议)。

当代码运行时,它会引用内存中的字符串,因此没有比非文字的额外成本。

所以是的,文字是被实习的。根据 String 的 Javadoc,

一个字符串池,最初是空的,由 String 类私下维护。

您可以在 String 上调用 intern() 以将其添加到此池中。从逻辑上讲,如果a.equals(b)a.intern() == b.intern(),因为.intern() 保证从唯一池返回。

例子:

class InternTest {
    // assuming InternTest is the only class, internPool.size = 0
    String x = "ABC"; // interned at class load, internPool.size = 1
    String y = "DEF"; // interned at class load, internPool.size = 2
    String z = "ABC"; // interned at class load, but match found - size = 2 still

    void foo() {
        // random int is just a mechanism to get something that I know won't
        // be interned at loadtime - could have loaded from file or database too
        int i = (new java.util.Random()).nextInt(1000) + 100;
        int j = i;
        String s = String.valueOf(i); // not yet interned, size = 2 still
        String t = String.valueOf(j); // not yet interned, size = 2 still

        String sIntern = s.intern(); // manually interned, size = 3 now
        String tIntern = t.intern(); // manually interned, match found, size = 3 still

        System.out.println("equals: " + (s.equals(t))); // should be true
        System.out.println("== raw: " + (s == t)); // should be false, different variables
        System.out.println("== int: " + (sIntern == tIntern)); // should be true, from unique pool

       System.out.println("x and z: " + (x == z)); // should be true, interned at class load
    }

    public static void main(String[] args) {
        (new InternTest()).foo();
    }

}

运行时的结果:

C:\Documents and Settings\glowcoder\My Documents>java InternTest
equals: true
== raw: false
== int: true
x and z: true

我应该指出,这个假设永远不会成立。 Java 语言本身有许多 Strings 将在我们的 Strings 有机会看到曙光之前被实习。但是,假设所有内容都是按顺序加载的,如果您只考虑被实习的字符串的增量,并假设与现有实习生没有冲突(我们都知道实习生可能很挑剔并且充满戏剧性,对吧?snicker)那么这些数字确实表明了字符串池大小的增量。

【讨论】:

  • 实际上,字符串实习确实发生在运行时(加载类时)。但是每个文字字符串只发生一次,复杂度 O(1),所以它不是性能问题。
  • 当我想到它时,这是有道理的——如果没有要加载的 JVM,我们怎么能有任何东西来保存 HashMap 呢?此外,由于intern() 是本机方法,因此无法在编译时完成。我会相应地更新我的答案。谢谢!
  • 感谢您的快速回答!我还有一些问题。在您的回答中,一个独特的池,其中一个是唯一的手段:1)池的每个元素都是唯一的 2)池是唯一的。如果在编译时有 2 个元素,然后在运行时声明第三个字符串而不进行实习,那么第三个字符串也会进入同一个池?
  • 第一个问题,只有一个池,池中的每个元素都是唯一的。其次,我将在帖子中展示一个代码示例。
  • 多么好的例子!非常感谢!
【解决方案2】:

1 - 当我们声明文字字符串时,我们在堆的字符串池中搜索是否有相同的字符串。这也是实习(String类的方法实习生)吗?

是的。这个过程称为实习。然而,它只发生一次 ...当包含文字的类被加载时。

2 - 在我看来,每个文字字符串声明都需要二进制搜索或其他东西,因此当 n 是池中现有字符串的数量时,它至少花费 log(n)。

不,它没有。池是一个哈希表。

...如果池中有很多字符串,则可能成本很高。

不,不会的。在字符串池哈希表中查找的成本是O(1)

... 从这个角度来看,声明许多文字字符串可能很危险。

与加载然后 JIT 编译类文件的其他成本相比,成本并不显着。在声明大量文字字符串时没有与性能相关的“危险”。

显然,字符串字面量对应的 String 对象会“永久”占用内存,您通常不希望不必要地浪费内存。但是,如果您需要使用那些常量字符串,则必须以某种方式表示它们。而其他表示它们的方式要么以其他方式使用内存,要么涉及其他运行时成本;例如从文件中读取它们或从数据库中检索它们的成本。

实习字符串文字的好处是堆不会被同一文字字符串的多个副本弄乱。这对于典型的 SE / EE 应用程序可能并不重要,但对于 ME 平台来说,堆内存非常宝贵,浪费它是一件坏事。


@RENO 询问字符串被拘留的次数。有两种情况:

  • String.intern() 的显式调用发生的次数与应用程序选择的次数一样多(或尽可能少)。

  • 对于字符串文字,javac 编译器将确保给定的.class 文件在其常量池中不包含任何字符串文字的多个副本。这意味着在许多地方都有给定文字的类只会在加载类时导致该文字被实习一次。但是,如果您在各自的源代码中有两个具有相同文字字符串的类,则它们都将在各自的常量池中具有字符串值,并且在加载各自的类时都将字符串保留。

【讨论】:

  • 感谢您的精彩回答!您能否详细解释一下您的解释:是的。这个过程称为实习。但是,它只发生一次......当加载包含文字的类时。我认为(实习的数量)与(文字字符串的数量 + 显式 intern() 方法的数量相同)。
  • 再次感谢!当我阅读你的解释时,我发现了一些与Java Language Specification 的对比部分,3.10.5 示例。在链接页面中,(Other.hello == hello) 的结果为真。我无法理解,因为您解释说:如果您有两个类...,但它们的结果是相同的。有什么我遗漏的地方吗?
  • @RENO - 我不明白你的困惑。正如 JLS 解释的那样,每个 String 文字都是被实习的。时期。我刚刚回答了您关于需要调用 intern() 以实现此目的次数的问题。
  • 嗯.. 我从您的解释中了解到,每个类都有各自的常量池。但是,根据示例, Other.hello 和 hello 引用相同的东西(我认为它们在同一个池中)。这是让我感到困惑的一点。
  • 也许您将类文件的“常量池”部分与 JVM 的“字符串池”混淆了。它们根本不是一回事。
猜你喜欢
  • 1970-01-01
  • 2010-09-27
  • 1970-01-01
  • 2017-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-05
  • 1970-01-01
相关资源
最近更新 更多