6

两个问题。

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

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

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


该类JavaDocjava.lang.String指出:

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

http://www.janeg.ca/scjp/lang/strLiteral.html评论:

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

4

2 回答 2

4

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

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

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

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

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

您可以调用intern()String 以将其添加到此池中。从逻辑上讲,如果a.equals(b)then a.intern() == b.intern(), since.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 将在我们String有机会看到曙光之前被实习。然而,假设一切都是按顺序加载的,如果你只考虑被实习的字符串的增量,并且假设没有与现有实习生发生冲突(我们都知道实习生可能很挑剔,充满戏剧性,对吧?窃笑)那么数字确实表明字符串池大小的增量。

于 2011-02-21T02:07:43.233 回答
4

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

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

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

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

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

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

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

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

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

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


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

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

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

于 2011-02-21T03:21:15.050 回答