Skip to content

Instantly share code, notes, and snippets.

@ThatJoeMoore
Created August 29, 2015 17:35
Show Gist options
  • Save ThatJoeMoore/d25950ecd5a21ed0f27a to your computer and use it in GitHub Desktop.
Save ThatJoeMoore/d25950ecd5a21ed0f27a to your computer and use it in GitHub Desktop.
Explanation of why you might want to use StringBuilder instead of String.concat().
import java.math.BigDecimal;
/**
* Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* (see http://creativecommons.org/licenses/by-nc-sa/4.0/)
*/
/**
* @author Joseph Moore (joseph_moore@byu.edu)
*/
public class StringVsBuilder {
public static void main(String[] args) {
int i = 1;
String name = "Joe Student";
BigDecimal number = new BigDecimal("12.34");
//This is the typical way to put together a string
String concat = "Hi there, " + name + ", my values are i = " + i + ", number: " + number;
/** That works, but it's actually very slow and memory-intensive. Why? Take a look at String.concat:
* public String concat(String str) {
* int otherLen = str.length();
* if (otherLen == 0) {
* return this;
* }
* int len = value.length;
* char buf[] = Arrays.copyOf(value, len + otherLen);
* str.getChars(buf, len);
* return new String(buf, true);
* }
* Internally, a string is just a wrapper around a char array, char[] value. When you do a + between two
* strings, concat() gets called on the strings. So, in the above example, it actually becomes:
* "Hi there".concat(name).concat(", my values are i = ").concat(Integer.toString(i)).concat(", number: ").concat(number.toString());
*
* So, we end up creating a total of 9 strings here (one per concat, each of our string literals, and one per toString call).
* Every time we call that .concat method, we're having to create and copy a new array, which is slow. Not super
* slow, but working with strings is something we do so often that it's usually considered part of the 'critical
* path' - the things we do so often that optimizations in them can have a measurable impact on our program.
*
* Now, if you actually compile this, you'll find out that it doesn't actually use .concat(). The compiler is smart
* enough to actually use StringBuilder! What!? The Java compile contains TONS of optimizations like that, so
* writing this code is actually okay and won't be inefficient. So, the actual code that will be generated will
* be something like this:
*
* String concat = new StringBuilder("Hi there, ").append(name).append(", my values are i = ").append(i)
* .append(", number: ").append(number).toString();
* Yay! Nice and optimized! Thanks, javac!
*
* However, the next example will still be super slow:
*/
boolean bool1 = true;
boolean bool2 = false;
boolean bool3 = bool1 ^ bool2;
String dynamic = "dynamic string:";
if (bool1) {
dynamic += " bool1 is true,";
}
dynamic += " Always put in,";
if (bool2) {
dynamic += " bool2 is true";
}
if (bool3) {
dynamic += " bool3 is true";
}
dynamic += " done";
/**
* That example can't be optimized by the compiler, because doing so would actually be slower. The compiler
* can only do an in-line StringBuilder replacement, so each time we assign to dynamic, it would have to turn
* it back into a string:
*/
String dynamicString = "dynamic string:";
if (bool1) {
dynamicString = new StringBuilder(dynamicString).append(" bool1 is true,").toString();
}
//etc.
/**
* Yikes. That would be even slower than string.concat, because we're actually basically doing the same thing, just
* with an extra new object creation (new Anything() is kind of slow). In both .concat and StringBuilder, we're
* creating a char array to hold the characters, copying both of our strings into it, and creating a new string
* using that char array. Thus, StringBuilder is only more efficient if we have more than one .append() call.
*
* So, let's get into how StringBuilder works, shall we?
*
* StringBuilder has two fields: a char array buffer and an integer length. It keeps the buffer at some
* unspecified, uncontrollable size and, as we perform various functions, copies values into the buffer.
* It then uses the length variable to keep track of the last spot in that array that it has copied something
* into. As you append things onto it, it will copy them into the char array. If it overflows that array,
* it will create a newer, longer one, then resume copying. All of that happens without your intervention, kind
* of like using an ArrayList. This is still more efficient than String.concat, though, as it doesn't have to create
* a new array every time, just sometimes.
*
* So, the efficient version of the dynamic example:
*/
StringBuilder sb = new StringBuilder("dynamic string:");
if (bool1) {
sb.append(" bool1 is true,");
}
sb.append(" Always put in,");
if (bool2) {
sb.append(" bool2 is true");
}
if (bool3) {
sb.append(" bool3 is true");
}
sb.append(" done");
String finished = sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment