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