Почему добавление «» в строку сохраняет память?

193

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

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

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

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

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

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

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

ОБНОВЛЕНИЕ: Как я могу очистить большую строку тогда? Будет ли data = new String (data.substring (0,100)) действовать?

hsmit
источник
Читая больше о вашем конечном намерении ниже: Откуда берется большая строка? Если вы читаете из файла или базы данных CLOB или что-то еще, тогда оптимальным будет чтение только того, что вам нужно во время синтаксического анализа.
PSpeed
4
Удивительно ... Я работаю в Java более 4-5 лет, но для меня это ново :). спасибо за информацию, братан
Парт
1
Есть тонкость в использовании new String(String); см. stackoverflow.com/a/390854/8946 .
Лоуренс Дол

Ответы:

159

Делать следующее:

data.substring(x, y) + ""

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

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

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

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

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

Брайан Агнью
источник
89
Это один из очень немногих случаев, когда String(String)конструктор (т.е. конструктор String, принимающий String в качестве входных данных) полезен: new String(data.substring(x, y))фактически делает то же самое, что и добавление "", но делает намерение несколько более ясным.
Йоахим Зауэр
3
просто, подстрока использует valueатрибут исходной строки. Я думаю, поэтому ссылка сохраняется.
Валентин Роше
@Bishiboosh - да, все верно. Я не хотел раскрывать особенности реализации, но это именно то, что происходит.
Брайан Агнью
5
Технически это деталь реализации. Но, тем не менее, это расстраивает и ловит много людей.
Брайан Агнью
1
Интересно, возможно ли оптимизировать это в JDK, используя слабые ссылки или что-то подобное. Если я последний человек, которому нужен этот символ [], а мне нужно только немного, создайте новый массив для внутреннего использования.
WW.
28

Если вы посмотрите на источник 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));
Паскаль Тивент
источник
1
Есть тонкость в использовании new String(String); см. stackoverflow.com/a/390854/8946 .
Лоуренс Дол
17

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

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

Крис Шут-Янг
источник
5

Я думаю, что this.smallpart продолжал ссылаться на данные, но почему?

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

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

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

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

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

Майкл Боргвардт
источник
Есть тонкость в использовании new String(String); см. stackoverflow.com/a/390854/8946 .
Лоуренс Дол
5

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

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

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

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

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

Кико Лобо
источник
3

Как документально подтверждено JWZ в 1997 году :

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

кругозор
источник
2

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

   String subtring = string.substring(5,23)

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

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

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

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

МДМА
источник
Есть тонкость в использовании new String(String); см. stackoverflow.com/a/390854/8946 .
Лоуренс Дол
2

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

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

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

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

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

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
лайка
источник
0

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

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

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

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

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

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

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

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

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

gnasher729
источник