JavaRush /Java блог /Random UA /Не давав спокою мені substring(..)
IgorBrest
33 рівень

Не давав спокою мені substring(..)

Стаття з групи Random UA
Власне, через сабжа дозволив собі покопатися в String.substring(..). І прийшов напевно до несподіваних результатів, якими вирішив поділитися з Вами, дорогі Джаворашівці. Подати на Ваш суд, так би мовити. Так ось. Є таке твердження, що рядок, створений за допомогою методу substring(..), використовує масив символів вихідного рядка. Ось, зокрема, витримка з нещодавно прочитаної статті "Довідник з java. Статичні рядки" всіма шановного articles :
Є зауваження щодо методу substring — рядок, що повертається, використовує той же байтовий масив, що і вихідний
Ну і звісно Лекції джавараш. Ось цитати з Декції 22:
Коли ми створюємо підрядок за допомогою методу substring, створюється новий об'єкт String. Але замість того, щоб зберігати посилання на масив з новим набором символів, цей об'єкт зберігає посилання на старий масив символів і разом із цим зберігає дві змінні, за допомогою яких визначає - яка частина оригінального масиву символів відноситься до нього. ... Коли створюється підрядок, масив символів не копіюється на новий об'єкт String. Натомість обидва об'єкти зберігають посилання на той самий масив символів. Але! Другий об'єкт зберігає ще дві змінні, до якого записано з якого і скільки символів цього масиву – його. ... Тому, якщо ти візьмеш рядок довжиною 10,000 символів і наробиш із неї 10,000 підрядок будь-якої довжини, то ці «підрядки» займатимуть дуже мало пам'яті, тому що ти можеш скористатися. масив символів не дублюється. Рядки, які мають займати купу місця, займатимуть буквально пару байт.
все зрозуміло розписано, навіть розжовано. Але, тому що я намагаюся підвищити знання англійської, то часто звертаюся до офіційної документації, і ось там я якось не зміг знайти підтвердження цьому факту ... Списавши це на свою неуважність, я все ж таки заглянув у вихідник substring () (благо IDEA дозволяє це зробити одним натисканням кнопки). public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); } заінтригований, я пішов далі: * Allocates a new {@code String} that contains characters from a subarray * of the character array argument. The {@code offset} argument is the * index of the first character of the subarray and the {@code count} * argument specifies the length of the subarray. The contents of the * subarray are copied; subsequent modification of the character array does * not affect the newly created string. public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); } де Arrays.copyOfRange - нативний метод, який повертає копію масиву з char... Цілком собі тривіальний код, і мені здалося очевидним, що просто створюється новий рядок з новим набором chars. або я щось не врахував... Так до кінця і не повіривши у свої висновки, я вирішив якось потестувати цей substring(), спираючись на фразу з лекції:
Тому, якщо ти візьмеш рядок довжиною 10,000 символів і наробиш із нього 10,000 підрядок будь-якої довжини, то ці «підрядки» займатимуть дуже мало пам'яті.
тільки замість 10_000 зробимо відразу 100_000_000, чого дріб'язатися. Накидав швидко такий код: і ось, що вийшло: тобто. щоразу під час створення нового рядка sub за допомогою bigString.substring(..) масив символів, саме ДУБЛІРУЄТЬСЯ. Інакше як пояснити таке зростання витрати пам'яті? Після цього особисто у мене відпали сумніви щодо роботи методу String.substsring() А у Вас? public class Test { public static void main(String[] args) { System.out.println("Начинаем:"); print(); System.out.println("********************************"); char[]big=new char[100_000_000];//создаем нормальный такой массив int j=0;//и заполняем этот массив всякой ерундой for (int k=0;k list=new ArrayList<>();//здесь будут ссылки на строки, что бы сборщик мусора не удалял //не используемые, по его мнению, строки. System.out.println("************************************"); System.out.println("Теперь будем создавть подстроки с помощью substring(..) и наблюдать," + "что же происходит с памятью"); for (int i = 2; i <10; i++) { //создаем подстроку, используя метод String.substring(..) String sub= bigString.substring(1,bigString.length()-1); //если этот метод не создает повністю новый массив символов, а только пользуется //исходным из bigString // то при создании новой строки sub мы не будем наблюдать ощутипый расход памяти list.add(sub);//эти ссылки мы должны где нибудь хранить, иначе сборщик мусора //избавится от неипользуемых объктов String System.out.print(String.format("Создаем %d-ую подстроку, при этом ", i - 1)); print(); } System.out.println("***************************************"); print(); } static void print(){ System.out.println("Памяти используется "+(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024/1024 + " mb"); } } Начинаем: Памяти используется 0 mb ******************************** создал большую строку bigString на основе массива big. Теперь: Памяти используется 382 mb ************************************ Теперь будем создавть подстроки с помощью substring(..) и наблюдать,что же происходит с памятью Добавляем 1-ую подстроку, при этом Памяти используется 573 mb Добавляем 2-ую подстроку, при этом Памяти используется 763 mb Добавляем 3-ую подстроку, при этом Памяти используется 954 mb Добавляем 4-ую подстроку, при этом Памяти используется 1145 mb Добавляем 5-ую подстроку, при этом Памяти используется 1336 mb Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Arrays.java:3658) at java.lang.String. (String.java:201) at java.lang.String.substring(String.java:1956) at com.javarush.test.tests.Test.main(Test.java:42) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Process finished with exit code 1
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ