Created
August 29, 2015 17:35
-
-
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().
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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