Основы:
String
- неизменный класс, его нельзя изменить.
StringBuilder
- это изменяемый класс, к которому можно добавлять, заменять или удалять символы и в конечном итоге преобразовывать в String
StringBuffer
является исходной синхронизированной версией StringBuilder
Вы должны предпочесть StringBuilder
во всех случаях, когда у вас есть только один поток, обращающийся к вашему объекту.
Подробности:
Также обратите внимание, что StringBuilder/Buffers
не магия, они просто используют массив в качестве вспомогательного объекта и что массив должен быть перераспределен, когда он будет заполнен. Обязательно создайте StringBuilder/Buffer
объекты, достаточно большие, чтобы их не приходилось постоянно менять каждый раз, когда вызывается .append()
.
Изменение размеров может стать очень вырожденным. Он в основном изменяет размеры резервного массива в 2 раза по сравнению с его текущим размером каждый раз, когда его необходимо расширить. Это может привести к тому, что большие объемы ОЗУ выделяются и не используются, когда классы StringBuilder/Buffer
начинают увеличиваться в размерах.
В Java String x = "A" + "B";
использует StringBuilder
за кадром. Так что для простых случаев нет смысла декларировать свое собственное. Но если вы создаете String
объекты большого размера, скажем, менее 4 КБ, тогда объявление StringBuilder sb = StringBuilder(4096);
гораздо эффективнее, чем конкатенация или использование конструктора по умолчанию , который содержит всего 16 символов. Если ваш String
будет меньше 10 КБ, инициализируйте его конструктором до 10 КБ, чтобы быть безопасным. Но если он инициализируется до 10 КБ, то вы пишете на 1 символ больше 10 КБ, он будет перераспределен и скопирован в массив из 20 КБ. Так что инициализация высокого лучше, чем низкого.
В случае автоматического изменения размера у 17-го символа резервный массив перераспределяется и копируется в 32 символа, у 33-го это происходит снова, и вы перераспределяете массив и копируете массив в 64 символа. Вы можете видеть, как это вырождается до лотов перераспределений и копий, что вы действительно пытаетесь избежать с помощью StringBuilder/Buffer
во-первых.
Это из исходного кода JDK 6 для AbstractStringBuilder
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
value = Arrays.copyOf(value, newCapacity);
}
Лучше всего инициализировать StringBuilder/Buffer
немного больше, чем вы думаете, если вам не нужно знать, насколько большим будет String
, но вы можете догадаться. Одно выделение чуть больше памяти, чем вам нужно, будет лучше, чем много перераспределений и копий.
Также остерегайтесь инициализации StringBuilder/Buffer
с String
, так как при этом будет выделен только размер строки + 16 символов, что в большинстве случаев просто начнет вырожденный цикл перераспределения и копирования, который вы пытаетесь выполнить. избежать. Следующее прямо из исходного кода Java 6.
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
Если у вас случайно окажется экземпляр StringBuilder/Buffer
, который вы не создали и не можете контролировать вызываемый конструктор, есть способ избежать вырожденного поведения перераспределения и копирования. Позвоните .ensureCapacity()
с размером, который вы хотите, чтобы ваш результирующий String
соответствовал.
Альтернативы:
В качестве примечания, если вы действительно тяжелые String
строения и манипуляции, есть гораздо более ориентированная на производительность альтернатива, называемая Веревки .
Другой альтернативой является создание StringList
реализации путем подкласса ArrayList<String>
и добавления счетчиков для отслеживания количества символов в каждом .append()
и других операциях мутации в списке, а затем переопределения .toString()
для создайте StringBuilder
нужного вам размера, циклически просматривайте список и формируйте выходные данные; вы даже можете сделать это StringBuilder
переменной экземпляра и «кэшировать» результаты .toString()
, и вам потребуется только сгенерировать его заново. когда что-то меняется.
Также не забывайте о String.format()
при создании фиксированных форматированных выходных данных, которые могут быть оптимизированы компилятором, поскольку они улучшают его.