Чому додавання "" до String зберігає пам'ять?


193

Скажімо, я використовував змінну з великою кількістю даних String data. Я хотів використати невелику частину цього рядка таким чином:

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

Через кілька годин налагодження (за допомогою візуалізатора пам'яті) я з'ясував, що поле об'єктів smallpartзапам'ятовувало всі дані з data, хоча воно містило лише підрядку.

Коли я змінив код на:

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

..блема була вирішена! Зараз у моєму додатку використовується дуже мало пам’яті!

Як це можливо? Хтось може це пояснити? Я думаю, що це.smallpart продовжував посилатися на дані, але чому?

ОНОВЛЕННЯ: Як тоді я можу очистити велику струну? Чи зроблять дані = нова строка (data.substring (0,100))?


Детальніше про ваш остаточний намір читайте нижче: Звідки в першу чергу береться велика струна? Якщо ви читаєте з файлу або бази даних CLOB або щось подібне, тоді оптимальним буде лише читання того, що вам потрібно під час розбору.
PSpeed

4
Дивовижно ... Я працюю в Java більше 4 - 5 років, все одно це для мене нове :). дякую за інформацію брато.
Парф

1
Існує тонкість використання new String(String); див. stackoverflow.com/a/390854/8946 .
Лоуренс Дол

Відповіді:


159

Виконайте наступне:

data.substring(x, y) + ""

створює новий (менший) об'єкт String і викидає посилання на String, створений substring (), таким чином, дозволяючи збирати сміття цього.

Важливо усвідомити те, що substring()дає вікно на існуючий рядок, а точніше, масив символів, що лежить в основі оригінальної рядки. Значить, вона буде споживати таку саму пам'ять, що і оригінальна струна. Це може бути вигідним за деяких обставин, але проблематично, якщо ви хочете отримати підрядку та розпорядитися оригінальною рядком (як ви з'ясували).

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

РЕДАКТУВАННЯ: Щоб відповісти на ваше додаткове запитання, конструювання нової рядки з підрядки зменшить споживання пам’яті за умови, що ви скористаєтеся будь-якими посиланнями на початкову рядок.

ПРИМІТКА (січень 2013 р.). Вищенаведена поведінка змінилася в Java 7u6 . Легка модель більше не використовується і substring()працюватиме так, як ви очікували.


89
Це один з небагатьох випадків, коли String(String)конструктор (тобто конструктор String, що бере в якості String як вхідний) корисний: new String(data.substring(x, y))робить ефективно те саме, що і додавати "", але це робить намір дещо зрозумілішим.
Йоахім Зауер

3
просто для точності, підрядка використовує valueатрибут початкового рядка. Я думаю, що тому довідка зберігається.
Валентин Рочер

@Bishiboosh - так, саме так. Я не хотів розкривати особливості впровадження, але саме це відбувається.
Брайан Агнеу

5
Технічно це деталь реалізації. Але все-таки це засмучує і наздоганяє багато людей.
Брайан Агнеу

1
Цікаво, чи можна оптимізувати це в JDK, використовуючи слабкі посилання або подібні. Якщо я є останньою людиною, яка потребує цього знаку [], і мені потрібно лише трохи його, створіть новий масив для мене, щоб використовувати його внутрішньо.
ВВ.

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

Я думаю, що це.smallpart продовжував посилатися на дані, але чому?

Оскільки рядки Java складаються з масиву char, зміщення старту та довжини (та кешованого хеш-коду). Деякі рядкові операції, такі як substring()створення нового об'єкта String, який розділяє масив char оригіналу і просто має різні поля зміщення та / або довжини. Це працює, тому що масив символів рядка ніколи не змінюється після його створення.

Це може зберегти пам'ять, коли багато підрядків посилаються на одну і ту ж основну рядок, не повторюючи частини, що перекриваються. Як ви помітили, в деяких ситуаціях він може зберігати дані, які вже не потрібні, від збирання сміття.

"Правильним" способом виправити це 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[]що представляє собою рядок . Це означає, що підрядок займе стільки ж пам’яті, скільки і початковий рядок .

Гаразд, але чому це + ""призводить до попиту на меншу кількість пам'яті, ніж без неї ??

Виконання +ввімкнення 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 або працює, уникає кожного разу копіювати мільйон символів. Є компроміс, і це добре, якщо ви знаєте, що таке компроміси.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.