Недавно в треде на Хабре (
http://habrahabr.ru/blogs/java/132500) было обсуждение того, насколько накладен метод String.substring. Мол, каждый раз при вызове этого метода создается новый объект, это жеж кошмар и все такое. Однако, давайте посмотрим чуть внимательнее.Любой Java-программист знает, как устроен внутри метод String (до определенных пределов :) - никто не знает всех деталей этого процесса, и то, насколько много вы знаете о строках в Java, и определяет вашу зрелость как Java-программиста, ха!).
Итак, у объекта класса String есть три поля. Offset, length, value. Последнее хранит массив символов, которые и представляют из себя строку, первый и второй - смещение от начала строки в массиве value[], и длину региона в этом массиве, которые и позволяют нам "вырезать" из массива символов value[] то, что можно назвать "срез текущей строки". Зачем мы храним в памяти эти два дополнительных свойства, смещение и длину строки? По разным причинам, одна из них - следование паттерну Flightweight, который позволяет нам использовать тот факт, что массив value[] - неизменяем, и расшаривать один общий экземпляр этого массива при вызове методов, которые возвращают нам часть исходной строки, например, substring.
Посмотрим внимательнее на метод String.substring:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }
Смотрим теперь по коду конструктор с такой же сигнатурой, видим:
// Package private constructor which shares value array for speed. String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
Вывод — при вызове метода Substring, новый объект String конечно создается, но массив символов, используемый для хранения данных, не копируется. В новом объекте String будут просто использованы другие значения для offset/lenght, и ссылка на тот же самый массив символов, относительно которых эти offset/length и берутся.
Небольшая тонкость относительно копирования строк, о которой не все знают. Иногда об этом не знают даже те, кто пишет об оптимизации использования памяти на Хабре ;)
и та же история с методом split
ОтветитьУдалитьДа, про него я забыл упомянуть. Спасибо за напоминание!
ОтветитьУдалитьДык, в той статье говорится о ситуации, когда мы прочитали длинную-предлинную строку, а работаем с небольшим кусочком этой строки. В такой ситуации был как раз таки выгодно скопировать маленький кусочек массива, чтобы большой массив был убран сборщиком мусора. И чтобы это произошло дядечка с хабрахабра предлагает делать
ОтветитьУдалитьss = new String(longString.substring(3))
вместо
ss = longString.substring(3)
Так мы заставляем создать новый маленький массив символов, чтобы старый был убран gc.
Так что слив не засчитан:)
P.S.
ОтветитьУдалитьА вообще, лучше использовать apache-commons StringUtils - там гораздо оптимальней ф-ии написаны, чем в стандартном классе.
Стандартный split, кстати, вообще редко когда стоит использовать: внутри этой ф-ии при каждом вызове компилится регексп. Это ужос!