1

We know that StringBuffer's default capacity is 16 and when we try to add 17th char it will be increased by following rule:

newCapacity = (current capacity + 1) *2;

StringBuffer sb = new StringBuffer();
sb.append("aaaaaaaaaaaaaaaa"); // length is 16
System.out.println(sb.capacity()); // it gives 16

If I add 17th char

StringBuffer sb = new StringBuffer();
sb.append("aaaaaaaaaaaaaaaaa"); // length is 17
System.out.println(sb.capacity()); // it gives 34

But confusing part is now

If I try to add 35 chars

StringBuffer sb = new StringBuffer();
sb.append("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // length is 35
System.out.println(sb.capacity()); // it gives 35 

capacity should have been increased by 70 at this point of time.

Interesting part is

StringBuffer sb = new StringBuffer();
sb.append("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // length is 34
sb.append("a"); // added 35th char
System.out.println(sb.capacity()); // it gives 70  which is correct

Can any one shed some light on this ?

Bifrost
  • 389
  • 3
  • 18
  • [this](https://stackoverflow.com/questions/3184244/stringbuilder-capacity) might throw some light! – absin Feb 06 '18 at 07:30
  • You are inconsistent between StringBuilder and StringBuffer. Please pick one and stick to it, or make it clear why you are changing between the two. – Andy Turner Feb 06 '18 at 07:42
  • It is StringBuffer only, that is my mistake in title – Bifrost Feb 06 '18 at 07:45

2 Answers2

3

The expandCapacity in StringBuffer does:

int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
    newCapacity = minimumCapacity;

Since value.length is 16 at that time in your 35 example, it will use 35 (given in minimumCapacity). In the last example however, during the last append the value.length is 34 and minimumCapacity is 35, so new capacity will be value.length * 2 + 2.

Jiri Tousek
  • 11,783
  • 5
  • 25
  • 41
  • then If I add 17th character StringBuffer sb = new StringBuffer(); sb.append("aaaaaaaaaaaaaaaaa"); // length is 17 System.out.println(sb.capacity()); // it gives 34( it should give 17th then according to your logic) – Bifrost Feb 06 '18 at 07:37
  • You're correct - `value.length` is actual capacity, not actual length, so it will be 16 in the first example (initial capacity), and also in the example form your comment. Then in your comment 16*2+2 will be higher than 17 so it will be used. – Jiri Tousek Feb 06 '18 at 08:23
1

The specifics may depend slightly on JDK version, but on my local version of 1.8.0_66:

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

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);
}

Note that value.length is actually capacity, not the length of the string being stored. The number of characters currently in the buffer is count! Also recall that value.length is initially 16, when calling new StringBuffer(). With those things in mind, let's do a little bit of stack tracing for each of the cases you presented.

For a string of size 17:

sb.append("12345678901234567")  
    if (str == null) -> false
    len = 17;
    ensureCapacityInternal(0 + 17)
        if (17 - 16 > 0) -> true
            expandCapacity(17)
                newCapacity = 16 * 2 + 2 = 34
                if (34 - 17 < 0) -> false
                value = Arrays.copyOf("", 34)
    str.getChars(0, 17, "", 17)
    return this
sb.build() -> "12345678901234567"
sb.capacity() -> 34

For a string of size 35:

sb.append("12345678901234567890123456789012345") 
    if (str == null) -> false
    len = 35;
    ensureCapacityInternal(0 + 35)
        if (35 - 16 > 0) -> true
            expandCapacity(35)
                newCapacity = 16 * 2 + 2 = 34
                if (34 - 35 < 0) -> true
                    newCapacity = 35
                value = Arrays.copyOf("", 35)
    str.getChars(0, 35, "", 35)
    return this
sb.build() -> "12345678901234567890123456789012345"
sb.capacity() -> 35

Note that the difference comes on the if (newCapacity - minimumCapacity < 0) line. If a string is appended that is longer than oldCapacity * 2 + 2, then newCapacity will be set to the length of the string to be appended.

In other words, when appending, if the buffer is not big enough to hold the existing text plus the appended text, it will check if (roughly) doubling in size would hold the new text. If that is still not enough, rather than recursively expanding, it will expand to exactly big enough.

This doesn't only happen with 35, though with strings much longer than that you probably wouldn't be running into the case where what you're appending is more than twice as long as your current capacity.

You would also see the same "length = capacity" if you were to do, say

StringBuffer sBuffer = new StringBuffer();
sBuffer.append("1234567890123456");
System.out.println(sBuffer.capacity()); // 16
sBuffer.append("1234567890123456789"); 
System.out.println(sBuffer.capacity()); // 35

But not

StringBuffer sBuffer = new StringBuffer();
sBuffer.append("1234567890123456");
System.out.println(sBuffer.capacity()); // 16
sBuffer.append("123456789012345678"); 
System.out.println(sBuffer.capacity()); // 34
sBuffer.append("1"); 
System.out.println(sBuffer.capacity()); // 70
nchen24
  • 502
  • 2
  • 9