String assembly by StringBuilder vs StringWriter and PrintWriter

StringWriter is what you use when when you want to write to a string, but you're working with an API that expects a Writer or a Stream. It's not an alternative, it's a compromise: you use StringWriter only when you have to.


Stylistically, the StringBuilder approach is cleaner. It is fewer lines of code and is using a class that was specifically designed for the purpose of building strings.

The other consideration is which is more efficient. The correct way to answer that would be to benchmark the alternatives, but (based on the source code) I would expect StringBuilder to be faster.

A StringWriter uses a StringBuffer (rather than a StringBuilder) under the hood to hold the characters written to the "stream". Thus using a StringWriter for string assembly is going to incur StringBuffer's synchronization overhead, whether your application needs it or not. But if your application does need the synchronization, then you should consider using a StringBuffer directly.


CPerkins has done some benchmarking (see his answer) and his results align with my intuition. They say that the difference between optimal StringBuilder vs StringWriter is ~5% which is likely to be insignificant for a typical application1. However, it is nice to know that the "style" and "performance" criteria are giving the same answer!

1 - While a typical application doesn't spend most of its time assembling strings, there are exceptions. However, one should not spend time optimizing this kind of thing based purely on guesswork. Profile your code first.


Okay, since the answers seem to stress the stylistic reasons for preferring one over the other, I decided to gen up a timing test.

Edit: Following robinst's comment above, I now have three methods: one which does PrintWriter(StringWriter)-style appending, and two which use StringBuilder: one with the append of the newline inside the append (as in the original: builder.append(thing + newline);), and the other does a separate append (as above: builder.append(thing).append(newline);).

I followed my usual benchmarking scheme: call the methods several (1000 in this case) times first to give the optimizer time to warm up, then call each a large number of times (100,000 in this case) in alternating sequence, so that any changes in the workstation's runtime environment are more fairly distributed, and run the test itself several times and average the results.

Oh, and of course the number of lines created and the length of the lines is randomized but held to be the same for each pair of calls, because I wanted to avoid any possible effects of buffer size or line size on the results.

The code for this is here: http://pastebin.com/vtZFzuds

Note: I have not yet updated the pastebin code to reflect the new test.

TL,DR? The average results for 100,000 calls to each method are quite close:

  • 0.11908 ms per call using StringBuilder (+newline inside the paren)
  • 0.10201 ms per call using StringBuilder (newline as a separate append)
  • 0.10803 ms per call using PrintWriter(StringWriter)

That's so close that the timing is nearly irrelevant to me, so I'm going to continue doing things the way I always did: StringBuilder(with separate append), for stylistic reasons. Of course, while working in this established and mature codebase, I'll mostly keep to the existing conventions, in order to minimize surprise.


Writers - PrintWriter writes to a stream. It does weird things like suppressing IOExceptions. System.out is one of these. - StringWriter is a Writer that writes to a StringBuffer (similar to ByteArrayOutputStream for OutputStreams)

StringBuilder - and of course StringBuilder is what you want if you are simply want to constructs strings in memory.

Conclusion

Writers by design are meant to push character data out a stream (usually to a file or through a connection) so being able to use Writers to simply assemble strings seems to be a bit of a side effect.

StringBuilder (and its synchronized sibling StringBuffer) are designed to assemble strings (read string bashing). If you look at the StringBuilder API you can see thatnot only can you do vanilla appending but also replace, reverse, insert etc. StringBuilder is your String construction friend.