Monday, January 15, 2007

Making Java Strings Simple

Your professor may tell you that using the String concatenation operator "+" can be inefficient. This is not entirely true.
String str = "foo" + "bar" + "Oh my, there must be a more efficient way to concatenate a String";

The above code is optimized by the compiler into something like this
StringBuffer/Builder* _temp = new StringBuffer/Builder().append("foo").append("bar")
.
append("Oh my, there must be a more efficient way to concatenate a String");
String str = _temp.toString();

So which is more efficient for you, typing 97 characters or 191 characters? More than 100% more typing for the same byte code.

There are exceptions though. The most important one being loops.
StringBuffer/Builder _temp = new StringBuffer/Builder();
while(moreDataToAppend()) {
_temp.append(someString);
}
String str = _temp.toString();

The above is preferred to this
String str = "";
while(moreDataToAppend()) {
str + someString;
}

because the second example creates a new StringBuffer/Builder for each iteration (read "more resources are used").


Here is some bytecode to back these claims. First the slower way:
7: getstatic #3; //Field i:I <----Here is the beginning of the loop
10: bipush 36
12: if_icmpge 51
15: new #4; //class java/lang/StringBuilder
18: dup
19: invokespecial #5; //Method java/lang/StringBuilder."":()V <----Here is
the allocation of the StringBuilder (this is invoked in every iteration of the loop)

22: aload_1
23: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)
Ljava/lang/StringBuilder;
26: getstatic #7; //Field data:[Ljava/lang/String;
29: getstatic #3; //Field i:I
32: aaload
33: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)
Ljava/lang/StringBuilder;
36: invokevirtual #8; //Method java/lang/StringBuilder.toString:()
Ljava/lang/String; <----This can also be move outside of the loop
39: astore_1
40: getstatic #3; //Field i:I
43: iconst_1
44: iadd
45: putstatic #3; //Field i:I
48: goto 7 <----Here is the end of the loop


And now the quicker way:
51: new #4; //class java/lang/StringBuilder
54: dup
55: invokespecial #5; //Method java/lang/StringBuilder."":()V <----Here is
the allocation of the StringBuilder

58: astore_2
59: iconst_0
60: putstatic #3; //Field i:I
63: getstatic #3; //Field i:I <----Here is the beginning of the loop
66: bipush 36
68: if_icmpge 94
71: aload_2
72: getstatic #7; //Field data:[Ljava/lang/String;
75: getstatic #3; //Field i:I
78: aaload
79: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)
Ljava/lang/StringBuilder;
82: pop
83: getstatic #3; //Field i:I
86: iconst_1
87: iadd
88: putstatic #3; //Field i:I
91: goto 63 <----Here is the end of the loop
94: aload_2
95: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;


As you can see in this example only the append() method is called inside the loop, whereas in the slower example

*Prior to Java 5 StringBuffer was used, now the preferred class is StringBuilder.