Java StringBuilder(StringBuffer) ensureCapacity(): почему он удваивается и увеличивается на 2?
Я искал об этом, но я не смог найти почему то StringBuilder это ensureCapacity()
метод не будет удлинять старую емкость, просто удваивая, но и добавляя два.
таким образом, когда емкость по умолчанию 16 заполнена, следующее удлиненное значение будет 34, если вся длина строки не превышает 34. Почему бы не 32?
мое лучшее предположение рассматривает для нулевого символа "u0000", но я не уверен. Кто-нибудь может сказать мне, почему?
5 ответов
Я считаю, что это связано с простым, если несколько тупым, способом обеспечения углового случая очень маленьких строк.
например, если у меня есть строка
""
и я удваиваю его только, у меня не будет достаточного размера, чтобы хранить в нем что-либо еще. Если я удвою его и добавлю небольшое постоянное число пробелов, я могу гарантировать, что мое новое значение больше, чем мое старое.
зачем тогда увеличивать его на два? Вероятно, небольшое улучшение производительности. От добавляя два вместо 1, я могу избежать промежуточного расширения для небольших расширений (от 0 до 10 символов, подробно описанных ниже)
"" => expand => "1" => expand => "123" expand => "1234567" expand => "123456789012345"
который 4 расширяется по сравнению с
"" => expand => "12" => expand => "123456" => expand => "123456789012"
который 3 расширяется. Это также хорошо работает для одной строки символов (расширяется до 10 символов)
"1" => expand => "1234" => expand => "1234567890"
пока 1 плановое расширение Чаре выглядит как
"1" => expand => "123" => expand => "1234567" => expand => "123456789012345"
наконец, добавленное приращение двух имеет тенденцию выравнивать слова около 50% времени, в то время как добавленные приращения одного или трое будут делать это примерно в 25% случаев. Хотя это не может показаться большим делом, некоторые архитектуры не могут вместить неприсоединившиеся чтения без дорогостоящих вызовов прерываний для перезаписи чтения в ЦП, что приводит ко всем видам проблем с производительностью.
Я думаю, что причина в сочетании
некоторые древние; -) эвристическая стратегия, как расширить возможности, особенно для короткие буферы,
документирование этой стратегии в ранних документах Java API,
Sun / Oracle очень осторожны, чтобы придерживаться однажды документированного поведения.
StringBuilder разделяет этот метод со своим предшественником StringBuffer, который читает (вероятно, из самые ранние начала, по крайней мере, в j2sdk1.4_02, который все еще существует в какой-то папке архива на моей машине):
/**
* Ensures that the capacity of the buffer is at least equal to the
* specified minimum.
* If the current capacity of this string buffer is less than the
* argument, then a new internal buffer is allocated with greater
* capacity. The new capacity is the larger of:
* <ul>
* <li>The <code>minimumCapacity</code> argument.
* <li>Twice the old capacity, plus <code>2</code>.
* </ul>
* If the <code>minimumCapacity</code> argument is nonpositive, this
* method takes no action and simply returns.
*
* @param minimumCapacity the minimum desired capacity.
*/
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
и он документирует точно поведение times-two plus-two, поэтому, даже если в то же время какой-то разработчик JRE нашел лучшую стратегию, нет никаких шансов реализовать ее здесь, потому что она не будет соответствовать документации.
Я полагаю, что выравнивание объекта является ключевым, потому что length * 2 + 2
-стратегия эффективна для памяти (см. объяснение ниже).
рассмотрим HotSpot JVM.
прежде всего, объекты java выровнены по 8 байтам, и массив char не является исключением.
во-вторых, sizeof(object header)
равна 8 bytes
on 32-разрядная JVM и 16 bytes
on 64-разрядная JVM с-XX: - UseCompressedOops.
таким образом, тело объекта должно быть выровнян 8 bytes
:objectBodySize(charArray) == sizeOf(arrayLength) + sizeOf(arrayValues) == (4 bytes) + (arrayLength * 2 bytes)
.
если длина старого массива даже, тогда новая длина массива всегда будет давать выравнивание нулевого размера.
примеры:
oldCharArrayLength == 6
затемnewCharArrayLength == 14
иobjectBodySize(newCharArray) == 4 + 14 * 2 == 32
oldCharArrayLength == 4
затемnewCharArrayLength == 10
иobjectBodySize(newCharArray) == 4 + 10 * 2 == 24
важно отметить, что - XX: + UseCompressedOops флаг доступен с 1.6, тогда как StringBuilder
и AbstractStringBuilder
С 1.5. Это означает, что стратегия выше с двумя дополнительными символами имеет нулевую стоимость памяти на 64-разрядная JVM до 1.6, а sizeof(object header) == 12 bytes
на 64-разрядная JVM с-XX: + UseCompressedOops.
Я загрузил исходный исходный код Java 1.5 из Oracle web, и он содержит следующие строки:
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
таким образом, по крайней мере, две вещи ясны:
- теория о том, что другие исправления были дополнительно добавлены, ложна (например, "нечетная (двойная + 2) семантика имела больше смысла, когда она была единственной строкой в функции" не истинна)
- скорее всего, первоначально это означало :"давайте освободим место хотя бы для еще одного персонажа, и давайте умножьте его на два!--7-->
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
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);
}
Примечание: значение.длина емкость StringBuffer, а не длина.
он не имеет ничего общего с нулевой строкой, потому что минимальная емкость составляет 16.
Я думаю, что выделение памяти стоит так много времени, и если мы часто вызываем ensureCapacity() с увеличением minimumCapacity, (capacity +1)*2 выделит немного больше памяти и может уменьшить дальнейшие выделения и сэкономить некоторое время.
рассмотрим начальную емкость как 16,
только с удвоением 16 , 32 , 64 , 128 , 256 , 512 , 1024 , 2048 , так далее...
с двойным +2 16 , 34 , 70 , 142 , 286 , 574 , 1150 , 2302 , так далее...
таким образом, память будет постепенно увеличиваться каждый раз и может уменьшить количество выделений памяти.