2010-10-19 1 views
4

Я понимаю basic idea инсценировки строк в Java, но я пытаюсь выяснить, в каких ситуациях это происходит, и что мне нужно будет сделать для моего собственного мухи.Когда это выгодно для мухи Струны в Java?

Несколько связанных:

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

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


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

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

ответ

6

Не используйте 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() в другое место.

+1

Почему карта, а не набор? HashSet кажется лучшим выбором для меня. –

+3

Если вы используете набор, как вы можете вернуть интернированную версию? то есть, что бы вы заменили 'pool.get()' на? – andersoj

+0

@Roland Illig: Я использую подобный подход с 'WeakReference ' для реализации кэша для долговременного приложения, которое обрабатывает много идентичных строк из сетевых сообщений. – andersoj

1

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

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(); 

See String#intern description in the API.

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

+0

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

1

Чтение String javadoc

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

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

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

String x = "string"; 

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

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

stringFromFile.intern(); 

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

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

1

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

Я нашел хороший базовый обзор интернирования (может быть, немного базовый, но все еще объясняет это просто отлично) на javatechniques.com.

2

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

(что не из даты):

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

(что может устареть):

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

16

Один из вариантов Guava дает вам здесь использование Interner вместо использования String.intern(). В отличие от String.intern(), Guava Interner использует кучу, а не постоянное поколение.Кроме того, у вас есть возможность интернировать String с слабыми ссылками, так что, когда вы закончите использовать эти String, 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 
+0

Это определенно сработало. Загрузка тестового файла с использованием памяти 100 000 записей с 194 МБ до 128 МБ (используется приложением, проверяется после запуска GC), а среднее время загрузки - с 14 до 11 секунд. –

+0

@bemace: Прохладный, рад это слышать. – ColinD

1

Когда ставить струну? Когда вы знаете, что у вас будет множество строк с низкой мощностью в данном месте.

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

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

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

1

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

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

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