Index Previous Next

String concatenation

The Java Language Specification notes that string concatenation may be implemented using a StringBuffer. Thus, using the JDK 1.0.2 javac compiler, the following Java code:

   public class test {

       public void g() {
           String world = "world" ;

           System.out.println("Hello "+world) ;
       }

   }

generates these bytecodes:

   .method public g()V
       .limit stack 3
       .limit locals 2
       ldc "world"
       astore_1
       getstatic java/lang/System/out Ljava/io/PrintStream;
       new java/lang/StringBuffer
       dup
       invokespecial java/lang/StringBuffer/<init>()V
       ldc "Hello "
       invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
       aload_1
       invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
       invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
       invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
       return
   .end method

But hold on, why use the StringBuffer constructor with no arguments, followed by an append when we could use the StringBuffer constructor which takes a String argument to get the first String into the StringBuffer? In fact, the JDK 1.1.3 compiler does just that:

   .method public g()V
       .limit stack 4
       .limit locals 2
       ldc "world"
       astore_1
       getstatic java/lang/System/out Ljava/io/PrintStream;
       new java/lang/StringBuffer
       dup
       ldc "Hello "
       invokespecial java/lang/StringBuffer/<init>(Ljava/lang/String;)V
       aload_1
       invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
       invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
       invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
       return
   .end method

By doing this we get rid of an append and save three bytes. However, this doesn't speed up the program because we're actually doing slightly more work. All that's happened is that the append is called from within the StringBuffer constructor rather than in our code. But the constructor also determines the length of the String argument and adds 16 to it to work how how long to make the buffer.

Here are timings for 50,000 iterations of a string concatenation:

       javac/classes     Bytes    Unoptimised       Optimised
                         saved     time (ms)        time (ms)

       102/102             3         7053             7369
       102/113             3         5095             5187

In both cases the optimised version takes longer than the unoptimised one. The 1.1.3 classes are somewhat faster than the 1.0.2 ones.

Because this optimisation results in slower code it has been placed in the rules.s file and is only used if jopt is called with the -s flag. The file also contains similar optimisations for cases where the first String is obtained from a variable.

Note that the optimisation is only applied for a String literal. If the String comes from a variable the JDK 1.1.3 compiler calls String.valueOf() on it before passing it to the StringBuffer constructor. This has the effect of turning a null String into the String "null". Otherwise the constructor would fail in its attempt to find the length of its argument.

Concatenating string literals

If the compiler is asked to concatenate two neighbouring string literals it doesn't (except in some special cases) take advantage of the obvious optimisation of merging the literals into one string. Instead it calls the StringBuffer append method to merge the strings at runtime. Hence,
   private int x ;
   private String s ;

   public void f() {
      s = x + "hello " + "world" ;
   }
is rendered as:
    aload_0
    new java/lang/StringBuffer
    dup
    aload_0
    getfield tester/x I
    invokestatic java/lang/String/valueOf(I)Ljava/lang/String;
    invokespecial java/lang/StringBuffer/<init>(Ljava/lang/String;)V
    ldc "hello "
    invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
    ldc "world"
    invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
    invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
    putfield tester/s Ljava/lang/String;
    return

Using an optimisation rule to merge the two string literals will save five bytes and avoid a method call.


Index Previous Next