Поиск стоимости интернирования строк и объявления литеральных строк - PullRequest
6 голосов
/ 21 февраля 2011

Два вопроса.

  1. Когда мы объявляем литеральные строки, мы ищем, есть ли такая же строка в пуле строк кучи. Это тоже интернирование (метод-интернат класса String)?

  2. На мой взгляд, каждое объявление литеральной строки нуждается в бинарном поиске или что-то в этом роде, поэтому стоит как минимум log (n) , когда n - это число существующих строк в бассейн. И если в бассейне много струн, это может быть дорого. (может быть, компромисс стоимости поиска и памяти?) С этой точки зрения может быть опасно объявлять mant литеральные строки. Насколько значительны затраты на поиск и почему java спроектирован таким образом (пул поиска при объявлении литеральных строк).

Вот что я имел в виду под фоном.


JavaDoc для java.lang.String класса сообщает:

Строки являются постоянными; их значения не могут быть изменены после их создания. Строковые буферы поддерживают изменяемые строки. Поскольку объекты String являются неизменяемыми, они могут использоваться совместно.

http://www.janeg.ca/scjp/lang/strLiteral.html комментарии:

Другими словами, поскольку компилятор знает, что исходное значение строки не может быть изменено после его создания, он может безопасно использовать существующие данные и избежать загромождения памяти дубликатами.

Ответы [ 2 ]

4 голосов
/ 21 февраля 2011

Вы путаете сложность времени компиляции со сложностью времени выполнения.

Когда класс загружен, да, он выполняет поиск, чтобы увидеть, существует ли каждый литерал (хотя я предполагаю, что он использует хеш-карту для O (1) поиск вместо вашего предложения).

Когда код выполняется, он имеет ссылку на строку в памяти, поэтому нет никаких дополнительных затрат, кроме не-литерального.

Так что да,литералы интернированы.Согласно Javadoc для String,

Пул строк, изначально пустой, поддерживается в частном порядке классом String.

Вы можете вызвать intern() для String, чтобы добавить его в этот пул.Логически следует, что если 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 имеет много String, которые были бы интернированы до того, как наши String когда-либо увидят свет.Однако, предполагая, что все загружается последовательно, если вы рассматриваете только интернализируемую дельту строк, и не допускаете столкновений с существующими интернами (мы все знаем, что интерны могут быть суетливыми и полными драмы, верно? snicker ) тогдачисла действительно указывают дельту размера пула строк.

3 голосов
/ 21 февраля 2011

1 - Когда мы объявляем литеральные строки, мы ищем, есть ли такая же строка в пуле строк кучи. Это тоже интернирование (метод intern класса String)?

Да. Этот процесс называется интернированием. Однако это происходит только один раз ... при загрузке класса, содержащего литерал.

2 - На мой взгляд, каждое объявление литеральной строки нуждается в бинарном поиске или чем-то подобном, поэтому стоит не менее log (n), когда n - это количество существующих строк в пуле.

Нет, это не так. В бассейне есть хеш-стол.

... А если в пуле много строк, это может быть дорого.

Нет, не будет. Стоимость поиска в хеш-таблице пула строк составляет O(1).

... С этой точки зрения может быть опасно объявлять много буквенных строк.

Стоимость не является значительной по сравнению с другими затратами на загрузку и последующую JIT-компиляцию файла класса. В объявлении большого количества литеральных строк нет «опасностей», связанных с производительностью.

Очевидно, что объекты String, соответствующие строковым литералам, занимают память «постоянно», и вы, как правило, не хотите тратить память без необходимости. Но если вам нужно использовать эти постоянные строки, они должны быть представлены как-то. И другие способы их представления либо используют память другими способами, либо включают другие затраты времени выполнения; например затраты на чтение их из файла или извлечение их из базы данных.

Преимущество интернирования строковых литералов состоит в том, что куча не загромождается несколькими копиями одной и той же литеральной строки. Это, вероятно, несущественно для типичных приложений SE / EE, но для платформ ME кучи памяти стоят дорого, и было бы плохо тратить ее впустую.


@ RENO спрашивает, сколько раз строки были интернированы. Есть два случая:

  • Явные вызовы String.intern() происходят столько (или несколько) раз, сколько приложение выбирает.

  • Для строковых литералов компилятор javac гарантирует, что данный файл .class не содержит нескольких копий какого-либо строкового литерала в своем постоянном пуле. Это означает, что класс, имеющий заданный литерал во многих местах, приведет к тому, что литерал интернируется только один раз, когда класс загружается. Однако, если у вас есть два класса с одинаковой литеральной строкой в ​​их соответствующем исходном коде, они оба будут иметь строковое значение в своих соответствующих пулах констант, и оба будут интернировать строку при загрузке соответствующих классов.

...