Search code examples
stringbuffer

How is capacity increased in StringBuffer?


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 ?


Solution

  • 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