Thursday, July 26, 2012

What's happening in the JVM level when you concatenate the Strings?


  Have you ever been told by someone about concatenate Strings in Java? Of course, there are many ways to do it but, what is the difference between each approach?

  In this blog post, I disassemble the example Java bytecodes (.class files) into Jasmin (simple assembler-like syntax with JVM instruction set). Each example demonstrates the different way to concatenate Strings(but not cover all the ways that exist in the world ^^).

Lets' disassemble and see what will be happened in the JVM level !

Note: I'm using JasminParser as a disassembler tool.


·         Concatenate Strings using '+' sing.

Java code

public void concatStrings(){
String a = "Hello";
String b = "Johnny";
String c = "Dew";

String result = a + b + c;
}

Jasmin

.method public concatStrings()V
.limit stack 3
.limit locals 5
.var 0 is this Lcom/crack/java/StringConcat; from Label0 to Label1
.var 1 is a Ljava/lang/String; from Label2 to Label1
.var 2 is b Ljava/lang/String; from Label4 to Label1
.var 3 is c Ljava/lang/String; from Label6 to Label1
.var 4 is result Ljava/lang/String; from Label1 to Label1

Label0:
ldc "Hello"
astore_1
Label2:
ldc "Johnny"
astore_2
Label4:
ldc "Dew"
astore_3
Label6:
new java/lang/StringBuilder
dup
aload_1
invokestatic java/lang/String/valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
aload_2
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
aload_3
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
astore 4
Label1:
return

.end method

When concatenate String using '+' sign in one statement, Java compiler will translate it to 
a StringBuilder operations.

·         Concatenate Strings using shortcut assignment operator (+=).

Java code

public void concatStrings(){
String a = "Hello";
String b = "Johnny";
String c = "Dew";

String result = null;
result += a;
result += b;
result += c;

}

Jasmin

.method public concatStrings()V
.limit stack 3
.limit locals 5
.var 0 is this Lcom/crack/java/StringConcatShortcutAssignmentOperator; from Label0 to Label1
.var 1 is a Ljava/lang/String; from Label2 to Label1
.var 2 is b Ljava/lang/String; from Label4 to Label1
.var 3 is c Ljava/lang/String; from Label6 to Label1
.var 4 is result Ljava/lang/String; from Label8 to Label1

Label0:
ldc "Hello"
astore_1
Label2:
ldc "Johnny"
astore_2
Label4:
ldc "Dew"
astore_3
Label6:
aconst_null
astore 4
Label8:
new java/lang/StringBuilder
dup
aload 4
invokestatic java/lang/String/valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
aload_1
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
astore 4

new java/lang/StringBuilder
dup
aload 4
invokestatic java/lang/String/valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
aload_2
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
astore 4

new java/lang/StringBuilder
dup
aload 4
invokestatic java/lang/String/valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
aload_3
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
astore 4
Label1:
return

.end method

When concatenate Strings using "+=" sign, the one "+=" called, the one StringBuilder will be created.

·         Concatenate Strings using StringBuilder.

Java code

public void concatStrings(){
String a = "Hello";
String b = "Johnny";
String c = "Dew";

StringBuilder builder = new StringBuilder(a);
builder.append(b);
builder.append(c);

String result = builder.toString();
}

Jasmin

.method public concatStrings()V
.limit stack 3
.limit locals 6
.var 0 is this Lcom/crack/java/StringConcatStringBuilder; from Label0 to Label1
.var 1 is a Ljava/lang/String; from Label2 to Label1
.var 2 is b Ljava/lang/String; from Label4 to Label1
.var 3 is c Ljava/lang/String; from Label6 to Label1
.var 4 is builder Ljava/lang/StringBuilder; from Label8 to Label1
.var 5 is result Ljava/lang/String; from Label1 to Label1

Label0:
ldc "Hello"
astore_1
Label2:
ldc "Johnny"
astore_2
Label4:
ldc "Dew"
astore_3
Label6:
new java/lang/StringBuilder
dup
aload_1
invokespecial java/lang/StringBuilder/<init>(Ljava/lang/String;)V
astore 4
Label8:
aload 4
aload_2
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
pop
aload 4
aload_3
invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
pop
aload 4
invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
astore 5
Label1:
return

.end method

Using StringBuilder is a straightforward way, what are we doing in the Java code will be mostly reflected the operations in the JVM level.


  This blog post is just a result from my curiousness, I just need to know what will be happened in the JVM level when concatenate the Strings in various ways. About the performance of each approach, the results are explicitly answer to this question. So, these are your choices.

Tuesday, July 3, 2012

Displaying Bitmaps Efficiently in Android



Before read this blog post, I highly recommend you to read the original article (Display Bitmaps Efficiently) from Android Developer website first. This is just my summary and diagrams depict the ideas from the original article.


To load Bitmap into memory in an efficient way, you will need to do 2 steps.

  1. Read Bitmap dimensions.
  2. Load a scaled down version into memory.

Here is a flowchart depicts the step to load Bitmap in an efficient way described in the Android Training document.

Fig.1 Steps to load Bitmap.


Processing Bitmaps Off the UI Thread

Processing Bitmap should be done in the worker Thread. The topic "Processing Bitmaps off the UI Thread" describes and show how to process Bitmap using AsyncTask. In the tutorial, each ImageView is referred using WeakReference inside the BitmapWorkerTask. Below is the diagram showing the relationship between BitmapWorkerTask and ImageView from the tutorial.


Fig.2 Relationship between BitmapWorkerTask and ImageView from the tutorial.

The interesting point from this topic is the way to handle concurrency issue. Using AsyncTask to process Bitmaps inside GridView or ListView  can cause ImageView to display wrong image when user scrolls the View. The idea to fix this problem is simple, when got ImageView from the adapter callback, simply check that this ImageView has a BitmapWorkerTask refers to it or not. If there is a BitmapWorkerTask refers to this ImageView then cancel the task before begin process new Bitmap.

The way to check that each ImageView has BitmapWorkerTask refers to it or not is wrapping the BitmapWorkerTask inside the dedicated Drawable subclass (the original article names this class "AsyncDrawable") then set the AsyncDrawable to the ImageView.


Fig.3 Relationship between BitmapWorkerTask, ImageView and AsyncDrawable.

Now when got ImageView from system callback (getView() method), we can check that this ImageView has BitmapWorkerTask refers to it or not, if it is, just simply cancel this task if it is not process the same Bitmap resource with the current request.


Fig.4 Flowchart illustrates the procedure to cancel an exiting BitmapWorkerTask before processing new Bitmap.

Also in BitmapWorkerTask, when processing Bitmap is finished, check if this task is cancelled or the current ImageView is already referred by another task (recycled) before setting image.


Fig.5 Flowchart illustrates the procedure to check current BitmapWorkerTask state before setting new image.


In case of the View components like GridView or ListView, when user scrolls back to the view that already recycled, you have to reprocess the Bitmap for that view again. Reprocess the Bitmap every time user scrolls back to the view cause your UI less responsive. Instead of reprocess, using cache is a better solution.

There are two types of cache: memory cache and disk cache.

For memory cache, LruCache (Least Recently Used Cache) was introduced in Android 3.1 (API level 12) and also available in support package for using in earlier Android version.

For disk cache, an example DiskLruCache class is included inside bitmap sample project and also in Android 4.0 source code (libcore/luni/src/main/java/libcore/io/DiskLruCache.java )

In the case of memory cache, the size of cache is very important. Please see this document for more information.