Почему емкость меняется на 112 в следующем примере?

В следующем коде...

StringBuffer buf = new StringBuffer("Is is a far, far better thing that i do");
System.out.println("buf = "+ buf);
System.out.println("buf.length() = " + buf.length());
System.out.println("buf.capacity() = " + buf.capacity());

buf.setLength(60);
System.out.println("buf = "+ buf);
System.out.println("buf.length() = " + buf.length());
System.out.println("buf.capacity() = " + buf.capacity());

buf.setLength(30);
System.out.println("buf = "+ buf);
System.out.println("buf.length() = " + buf.length());
System.out.println("buf.capacity() = " + buf.capacity());

... вывод:

buf = Is is a far, far better thing that i do 
buf.length() = 39
buf.capacity() = 55
buf = Is is a far, far better thing that i do
buf.length() = 60
buf.capacity() = 112
buf = Is is a far, far better thing 
buf.length() = 30
buf.capacity() = 112

5 ответов

Решение

Рассмотрим, как обычно используется StringBuffer. Когда значение String, которое нам нужно сохранить в StringBuffer, превышает текущую емкость, текущая емкость увеличивается. Если бы алгоритм только увеличивал емкость до требуемого количества, то StringBuffer был бы очень неэффективным. Например:

 buf.append(someText);
 buf.append(someMoreText);
 buf.append(Another100Chars);

может потребоваться увеличение емкости три раза подряд. Каждый раз, когда емкость увеличивается, базовая структура данных (массив) должна перераспределяться в памяти, что включает выделение большего объема оперативной памяти из кучи, копирование существующих данных, а затем в конечном итоге сбор мусора ранее выделенной памяти. Чтобы уменьшить частоту этого, StringBuffer удваивает свою емкость при необходимости. Алгоритм перемещает емкость от n до 2n+2. Вот исходный код из AbstraceStringBuilder, где реализован этот метод:

/**
 * This implements the expansion semantics of ensureCapacity with no
 * size check or synchronization.
 */
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

Каждый раз, когда вы добавляете StringBuffer или вызываете setLength, этот метод вызывается:

public synchronized void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > value.length) {
        expandCapacity(minimumCapacity);
    }
}

StringBuffer вызывает в нескольких точках метод expandCapacity, Если он не увеличит емкость, ему придется выделять новый массив каждый раз, когда вы меняете Stringbufferс ценностью. Так что это какая-то оптимизация производительности.

Из руководства:

ensureCapacity

public void sureCapacity (int minimalCapacity)

Гарантирует, что емкость как минимум равна указанному минимуму. Если текущая емкость меньше аргумента, то выделяется новый внутренний массив с большей емкостью. Новая емкость больше:

* The minimumCapacity argument.
* Twice the old capacity, plus 2. 

Если аргумент MinimCapacity не положительный, этот метод не предпринимает никаких действий и просто возвращает.

Параметры: минимальная емкость - минимальная желаемая емкость.

Вызов setLength(60) вызовет ensureCapacity(60) называться1.

ensureCapacity полагается на "удвоение массива", что означает, что он будет (по крайней мере) удваивать емкость каждый раз, когда его нужно увеличить. Точное определение задокументировано в Документе Java дляensureCapacity:

Гарантирует, что емкость как минимум равна указанному минимуму. Если текущая емкость меньше аргумента, то выделяется новый внутренний массив с большей емкостью. Новая емкость больше:

  • Аргумент минимальной емкости.
  • Дважды старую емкость, плюс 2.

Если аргумент MinimCapacity не положительный, этот метод не предпринимает никаких действий и просто возвращает.

В вашем конкретном случае второе выражение (выделено жирным шрифтом) больше, чем запрашиваемая емкость, поэтому оно будет использовано. Так как 2*55 + 2 равняется 112, это будет новая емкость.

Связанный вопрос:

1) На самом деле, он будет вызывать exteCapacity, но он ведет себя так же, как и обеспечивает емкость.

Это случай "прочитайте бесплатное руководство". От Javadoc для StringBuffer -

public StringBuffer (String str)

Создает строковый буфер, инициализированный для содержимого указанной строки. Начальная емкость строкового буфера равна 16 плюс длина строкового аргумента.

что объясняет, почему изначально 55. Затем

public void sureCapacity(int minimalCapacity)

Гарантирует, что емкость как минимум равна указанному минимуму. Если текущая емкость меньше аргумента, то выделяется новый внутренний массив с большей емкостью. Новая емкость больше:

• Аргумент минимальной емкости.

• Дважды старая емкость плюс 2.

Если аргумент MinimCapacity не положительный, этот метод не предпринимает никаких действий и просто возвращает.

объясняет, почему он меняется на 112

public synchronized void setLength(int newLength) {
    super.setLength(newLength);
}

в супер:

public void setLength(int newLength) {
    if (newLength < 0)
        throw new StringIndexOutOfBoundsException(newLength);
    ensureCapacityInternal(newLength);
....

Затем:

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
....

И наконец:

void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
....
Другие вопросы по тегам