Собственно, по причине сабжа позволил себе покопаться в 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, чего мелочиться. Накидал побыстрому такой код:
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;klist=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
т.е. каждый раз при создании новой строки sub при помощи bigString.substring(..) массив символов, как раз ДУБЛИРУЕТСЯ. Иначе как объяснить такой рост расхода памяти?.
После этого лично у меня отпали всякие сомнения относительно работы метода String.substsring()
А у Вас?
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
не создавая в памяти подстроки как написано в статье JavaRush)
а после 7u6 — занимают 24 байта:
Сделали это не для экономии памяти :), к тому же сильно проиграли в скорости работы substring() — теперь надо выделять память и копировать(затратно),
а не просто инициировать начало(String.offset) и смещение(String.count).
Цель — избавится от утечек и расхода памяти. Так как создав подстроку:
1) трудно контролировать необходимость надобности удаления исходной строки (зависимость)
2) если исходная строка огромная, а мы сабстрингаем 2-3 символа, то и исходная строка вынуждена оставаться и занимать память.
Кому интересны подробности на русском языке, потратьте час на:
видео: www.youtube.com/watch?v=SZFe3m1DV1A и
слайды: shipilev.net/talks/jpoint-April2015-string-catechism.pdf
stackoverflow.com/questions/20260140/how-to-detect-whether-string-substring-copies-the-character-data
таакая штука имела место в версиях Java ниже седьмой.