14

It is often argued that avoiding creating objects (especially in loops) is considered good practice.

Then, what is most efficient regarding StringBuffer?

StringBuffer sb = new StringBuffer();
ObjectInputStream ois = ...;

for (int i=0;i<1000;i++) {

    for (j=0;i<10;j++) {
        sb.append(ois.readUTF());
    }
    ...

    // Which option is the most efficient? 
    sb = new StringBuffer(); // new StringBuffer instance?
    sb.delete(0,sb.length()); // or deleting content?

}

I mean, one could argue that creating an object is faster then looping through an array.

Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453

3 Answers3

16

First StringBuffer is thread-safe which will have bad performance compared to StringBuilder. StringBuilder is not thread safe but as a result is faster. Finally, I prefer just setting the length to 0 using setLength.

sb.setLength(0)

This is similar to .delete(...) except you don't really care about the length. Also probably a little faster since it doesn't need to 'delete' anything. Creating a new StringBuilder (or StringBuffer) would be less efficient. Any time you see new Java is creating a new object and placing that on the heap.

Note: After looking at the implementation of .delete and .setLength, .delete sets length = 0, and .setLength sets every thing to '\0' So you may get a little win with .delete

Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123
  • If the `StringBuilder` gets a very large internal buffer allocated which is wasted because an early loop iteration produces a very large string, and later iterations don't, then you might end up thrashing longer than necessary, but otherwise keeping the buffer is a net win since if later iterations produce strings almost the same size as earlier iterations, you are avoiding some buffer copies. – Mike Samuel Aug 24 '11 at 00:12
  • 1
    `delete` is not O(N) when the delete clears the entire contents. – Mike Samuel Aug 24 '11 at 00:13
  • This is true, I am updating my answer. But only when len == count which is true if you are deleting all. – Amir Raminfar Aug 24 '11 at 00:21
  • 1
    `setLength` will not sets everything to '\0'. Only if the new size is > to the current one and only for the part of the internal buffer that was extended. – benbenw Aug 25 '16 at 07:26
  • Surprisingly, `StringBuilder.delete()` is 26% faster than `StringBuilder.setLength(0)` per JMH. It just goes to show you, you can't determine what's faster simply by looking at the code. – Gili Apr 05 '20 at 04:26
3

Just to amplify the previous comments:

From looking at source, delete() always calls System.arraycopy(), but if the arguments are (0,count), it will call arraycopy() with a length of zero, which will presumably have no effect. IMHO, this should be optimized out since I bet it's the most common case, but no matter.

With setLength(), on the other hand, the call will increase the StringBuilder's capacity if necessary via a call to ensureCapacityInternal() (another very common case that should have been optimized out IMHO) and then truncates the length as delete() would have done.

Ultimately, both methods just wind up setting count to zero.

Neither call does any iterating in this case. Both make an unnecessary function call. However ensureCapacityInternal() is a very simple private method, which invites the compiler to optimize it nearly out of existence so it's likely that setLength() is slightly more efficient.

I'm extremely skeptical that creating a new instance of StringBuilder could ever be as efficient as simply setting count to zero, but I suppose that the compiler might recognize the pattern involved and convert the repeated instantiations into repeated calls to setLength(0). But at the very best, it would be a wash. And you're depending on the compiler to recognize the case.

Executive summary: setLength(0) is the most efficient. For maximum efficiency, pre-allocate the buffer space in StringBuilder when you create it.

Edward Falk
  • 9,991
  • 11
  • 77
  • 112
1

The delete method is implemented this way:

public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

As you can see it doesn't iterate through the array.

Ozwizard
  • 99
  • 1
  • 5
Tamás
  • 298
  • 3
  • 14
  • 2
    What do you think `System.arraycopy` does?? – Amir Raminfar Aug 24 '11 at 00:06
  • @Mike I am not sure what you mean `len` will be 0. It is computed from `end - start` which will `sb.length() - 0`. What you mean to say `count-end` will be 0 which is true. But generally you don't compute O(n) for only one set of params. – Amir Raminfar Aug 24 '11 at 00:19
  • @Amir, sorry I think I was right about no significant work being done but I was wrong about why. When `start` is 0 and `end` is `count` aka `this.length()`, then you end up doing `System.arraycopy(value, count, value, 0, 0)`. The last 0 means it is copying 0 bytes from index count to index 0. – Mike Samuel Aug 24 '11 at 00:54
  • I've looked at the source code. In this particular case, arraycopy() will do nothing but return. All you've done is waste the overhead of a function call. – Edward Falk Jan 08 '13 at 00:05