Когда выгодно использовать висячие строки в Java? - PullRequest
4 голосов
/ 20 октября 2010

Я понимаю базовую идею интернирования java's String, но я пытаюсь выяснить, в каких ситуациях это происходит, и в каких случаях мне нужно было бы выполнять мой собственный взвешивание.

В некотором роде:

Вместе они говорят мне, что String s = "foo" - это хорошо, а String s = new String("foo") - плохо, но о других ситуациях не упоминается.

В частности, если я проанализирую файл (скажем, csv), который имеет много повторяющихся значений, покроет ли меня интернирование строк в Java или мне нужно что-то сделать самому? Я получил противоречивый совет о том, применяется ли String interning здесь в моем другом вопросе


Полный ответ состоял из нескольких фрагментов, поэтому я подведу итог:

По умолчанию java интернирует только те строки, которые известны во время компиляции. String.intern(String) можно использовать во время выполнения, но он не очень хорошо работает, поэтому он подходит только для меньшего числа String с, которое, как вы уверены, будет повторяться много . Для больших наборов Струн это Guava на помощь (см. Ответ ColinD).

Ответы [ 8 ]

20 голосов
/ 20 октября 2010

Один из вариантов Гуава дает здесь возможность использовать Интерн вместо использования String.intern().В отличие от String.intern(), гуава Interner использует кучу, а не постоянное поколение.Кроме того, у вас есть возможность интернировать String s со слабыми ссылками, так что когда вы закончите использовать эти String s, Interner не предотвратит их сборку мусора.Если вы используете Interner таким образом, что он отбрасывается, когда вы закончите со строками, вы можете просто использовать сильные ссылки с Interners.newStrongInterner() вместо этого для возможно лучшей производительности.

Interner<String> interner = Interners.newWeakInterner();
String a = interner.intern(getStringFromCsv());
String b = interner.intern(getStringFromCsv());
// if a.equals(b), a == b will be true
7 голосов
/ 20 октября 2010

Не используйте String.intern () в вашем коде. По крайней мере, если вы можете получить 20 или более различных строк. По моему опыту, использование String.intern замедляет работу всего приложения, когда у вас есть несколько миллионов строк.

Чтобы избежать дублирования String объектов, просто используйте HashMap.

private final Map<String, String> pool = new HashMap<String, String>();

private void interned(String s) {
  String interned = pool.get(s);
  if (interned != null) {
    return interned;
  pool.put(s, s);
  return s;
}

private void readFile(CsvFile csvFile) {
  for (List<String> row : csvFile) {
    for (int i = 0; i < row.size(); i++) {
      row.set(i, interned(row.get(i)));
      // further process the row
    }
  }
  pool.clear(); // allow the garbage collector to clean up
}

С этим кодом вы можете избежать дублирования строк для одного файла CSV. Если вам нужно избежать их в более широком масштабе, позвоните pool.clear() в другое место.

2 голосов
/ 20 октября 2010

Эта информация может быть устаревшей, и у меня больше нет кода для ее резервного копирования ...

(что не устарело):

Чтение в строках через сканер, ридер и т. Д. Не интернировано. Только строковые литералы интернированы (конечно, это до реализации, я не думаю, что есть что-то, что говорит, что они не могут интернироваться).

(что может быть устаревшим):

Я написал программу, в которой хотел быть быстрым и использовать как можно меньше памяти. Я пытался с и без стажера при каждом чтении строки из файла. Стажер способ значительно дольше, чем без использования стажера, настолько, что я решил не делать стажера. Если производительность имеет значение, попробуйте синхронизировать ваш код с / без стажера. Вы также можете проверить использование памяти (для этого подойдет профилировщик) с / без стажера и посмотреть, имеет ли компромисс разницу для вас.

1 голос
/ 14 сентября 2017

Полагаю, после введения ключа -XX:StringTableSize, String.intern() должен быть пригоден для использования. Причиной ужасной скорости является то, что таблица имеет фиксированный размер и безнадежно перегружена строковыми константами даже без интернирования.

Размер таблицы должен быть простым!

Использование таблицы большего размера должно сделать String.intern() почти таким же быстрым, как и любая другая хеш-таблица. Не совсем из-за использования по модулю вместо побитового и. Положительным моментом является то, что объем памяти значительно снижается (не нужно ни Map.Entry, ни WeakReference).

1 голос
/ 20 октября 2010

Когда интернировать строку?Когда вы знаете, что у вас будет МНОЖЕСТВО строк с НИЗКОЙ кардинальностью в данном месте.

Например ... код пакетной обработки.Вы планируете обработать 100 миллионов строк, многие из созданных POJO имеют поле (скажем, поле CITY для объекта Person), которое будет только одним из нескольких возможных ответов (Нью-Йорк, Чикаго и т. Д.).Слишком много вариантов, чтобы сделать ENUM, но вам действительно не нужно создавать 45 миллионов строк с надписью New York.Вы МОЖЕТЕ использовать интернирование или какой-то вариант домашнего проката (слабая справочная карта, вероятно, лучше, чем String.intern), чтобы уменьшить объем используемой памяти.

Вы можете сэкономить место в памяти за счет возможной работы ЦП ...в некоторых местах это может стоить, но трудно сказать.GC довольно быстрый, ваши дублирующиеся строки получат GCed, как только они будут использованы.

Так что, если вы когда-нибудь попадете в место, где вы наткнетесь на стену памяти, и у вас будут Строки с низкой кардинальностью ... вы можете рассмотреть возможность интернирования.

1 голос
/ 20 октября 2010

Насколько мне известно, интернирование строк происходит автоматически только для строковых литералов, все остальные должны быть программно интернированы с использованием метода {@link java.lang.String # intern ()}.Таким образом, конструирование String с помощью конструктора с использованием уже интернированного литерала String создает новую строку, которая не интернирована, но содержит то же содержимое, что и интернализованный литерал, на котором он был построен.стажировка (может быть немного простой, но все же объясняет это просто отлично) на javatechniques.com .

1 голос
/ 20 октября 2010

Чтение String javadoc

Все литеральные строки и строковые константные выражения интернированы.

Это заставляет меня поверить, что строкивы получаете из файла, после того как ваша программа скомпилирована, она не будет автоматически интернирована.

Если вы сказали что-то вроде:

String x = "string";

, который будет интернирован компилятором, потому что он виден во время компиляции.

Если вы знаете, что определенные строки очень распространеныво входном файле вы можете вызвать

stringFromFile.intern();

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

Вы можете попробовать эксперимент с вашим конкретным входом и посмотреть, что произойдет в лучшем случае, если вы вручную интернируете некоторые данные и сравните это с поведением по умолчанию без интернирования.

1 голос
/ 20 октября 2010

В большинстве случаев строка создается из массива byte или char (если в коде нет строкового литерала), поэтому вы можете проверить его.

    String s = "test";
    String s1 = new String(s.getBytes());
    String s2 = String.valueOf(s.toCharArray());
    String s3 = new String(s.toCharArray());

    System.out.println(s == s1);
    System.out.println(s == s2);
    System.out.println(s == s3);

Печать false для всех. Но вы можете явно интернировать строку, если хотите, у вас будет много повторяющихся значений. Если вы добавите это в приведенный выше пример, он напечатает true для всех трех сравнений

    s1 = s1.intern();
    s2 = s2.intern();
    s3 = s3.intern();

См. String # intern description в API .

редактировать
Так будет ли использование intern () для каждого прочитанного значения разумным способом для достижения веса?
Да, при условии, что нет никаких ссылок на старую строку. Если старая ссылка на строку больше нигде не используется, она будет собираться мусором.

...