Почему добавление "" в строку сохраняет память? - PullRequest
193 голосов
/ 27 января 2010

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

this.smallpart = data.substring(12,18);

После нескольких часов отладки (с визуализатором памяти) я обнаружил, что поле объектов smallpart запоминает все данные из data, хотя оно содержит только подстроку.

Когда я изменил код на:

this.smallpart = data.substring(12,18)+""; 

.. проблема была решена! Теперь мое приложение использует очень мало памяти!

Как это возможно? Кто-нибудь может объяснить это? Я думаю, что this.smallpart продолжал ссылаться на данные, но почему?

UPDATE: Как я могу очистить большую строку тогда? Будет ли data = new String (data.substring (0,100)) делать это?

Ответы [ 9 ]

159 голосов
/ 27 января 2010

Выполнение следующих действий:

data.substring(x, y) + ""

создает новый (меньший) объект String и выбрасывает ссылку на строку, созданную substring (), что позволяет собирать мусор.

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

Взгляните на метод substring () в источнике JDK String для получения дополнительной информации.

РЕДАКТИРОВАТЬ: Чтобы ответить на ваш дополнительный вопрос, построение новой строки из подстроки уменьшит потребление памяти, при условии вы удаляете любые ссылки на исходную строку.

ПРИМЕЧАНИЕ (январь 2013 г.). Вышеуказанное поведение изменилось в Java 7u6 . Шаблон flyweight больше не используется, и substring() будет работать так, как вы ожидаете.

28 голосов
/ 27 января 2010

Если вы посмотрите на источник substring(int, int), вы увидите, что он возвращает:

new String(offset + beginIndex, endIndex - beginIndex, value);

, где value - оригинал char[]. Таким образом, вы получаете новую строку, но с той же базовой char[].

Когда вы делаете, data.substring() + "", вы получаете новую строку с новым , лежащим в основе char[].

На самом деле, ваш вариант использования - единственная ситуация, когда вы должны использовать конструктор String(String):

String tiny = new String(huge.substring(12,18));
17 голосов
/ 27 января 2010

Когда вы используете substring, он фактически не создает новую строку. Он по-прежнему относится к вашей исходной строке с ограничением смещения и размера.

Итак, чтобы собрать исходную строку, вам нужно создать новую строку (используя new String или то, что у вас есть).

5 голосов
/ 27 января 2010

Я думаю, что это ссылка на данные, но почему?

Поскольку строки Java состоят из массива char, начального смещения и длины (и кэшированного hashCode). Некоторые операции String, такие как substring(), создают новый объект String, который разделяет массив char оригинала и просто имеет различные поля смещения и / или длины. Это работает, потому что массив char строки никогда не изменяется после того, как он был создан.

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

"Правильный" способ исправить это - конструктор new String(String), т.е.

this.smallpart = new String(data.substring(12,18));

Кстати, в целом лучшим решением было бы избегать, во-первых, очень больших строк и обрабатывать любой ввод небольшими кусками, по несколько КБ за раз.

5 голосов
/ 27 января 2010

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

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

Итак, когда вы создали новую строку с этим кодом:

this.smallpart = data.substring(12, 18) + ""; 

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

3 голосов
/ 27 января 2010

Как задокументировано jwz в 1997 :

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

2 голосов
/ 02 июня 2013

Во-первых, вызов java.lang.String.substring создает новое окно для оригинала String с использованием смещения и длины вместо копирования значительной части базового массива.

Если мы более подробно рассмотрим метод substring, мы заметим строковый конструктор , вызывающий String(int, int, char[]) и передающий ему целое char[], которое представляет строку . Это означает, что подстрока будет занимать столько же памяти, сколько и исходная строка .

.

Хорошо, но почему + "" требует меньше памяти, чем без нее?

Выполнение + на strings осуществляется с помощью вызова метода StringBuilder.append. Посмотрите, как реализация этого метода в AbstractStringBuilder классе скажет нам, что он, наконец, делает arraycopy с той частью, которая нам действительно нужна (substring).

Любой другой обходной путь ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
2 голосов
/ 29 апреля 2010

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

   String subtring = string.substring(5,23)

Поскольку вы используете пространство только для хранения больших строк, но если вы извлекаете всего несколько маленьких строк из потерянных больших строк, то

   String substring = new String(string.substring(5,23));

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

То, что вы называете new String, является полезным напоминанием о том, что вы действительно получаете новую строку, а не ссылку на исходную.

0 голосов
/ 27 марта 2015

Добавление "" к строке будет иногда экономить память.

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

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

Затем я создаю 1000 строк, содержащих все абзацы.

Затем я создаю 10000 строк, содержащих все предложения.

Затем я создаю 100 000 строк, содержащих все слова.

Я до сих пор использую только 1 000 000 символов. Если вы добавите «» к каждой главе, абзацу, предложению и слову, вы используете 5 000 000 символов.

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

И снова все по-другому, если у вас есть миллионная строка символов и вы убираете табуляцию и пробелы с обоих концов, делая, скажем, 10 вызовов для создания подстроки. То, как работает или работает Java, позволяет избежать копирования миллиона символов каждый раз. Есть компромисс, и хорошо, если вы знаете, что это за компромиссы.

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