What's the difference among String, StringBuilder and StringBuffer, in Java?
Labs
Lab 1 Append Method
public static void app(String s){
s += " world";
}
public static void app(StringBuilder s){
s.append(" world");
}
public static void app(StringBuffer s){
s.append(" world");
}
public static void main(String args[]){
String string = new String("hello");
StringBuilder stringBuilder = new StringBuilder("hello");
StringBuffer stringBuffer = new StringBuffer("hello");
app(string);
app(stringBuilder);
app(stringBuffer);
System.out.println(string);
System.out.println(stringBuilder);
System.out.println(stringBuffer);
}
Output
hello
hello world
hello world
Explanation
- String: String is taken as reference. The string in main() still refers to the old one, which is "hello". Some may say that string is a reference of "hello", after app() the actual value should be changed. However, s += " world" means, s = s + "world". So s is reasigned, after which, s in method app() refers to a new one, "hello world". But string in main() still refers to the old one, "hello".
- StringBuilder: stringBuilder is passed as a reference, which refers to "hello". After app(), the actual value is changed. In app(), if we have s = new StringBuilder("world"), we will get the result, "hello" (same as above).
- StringBuffer: same as StringBuilder.
Lab 2 Multiple Thread
static String string = "";
static StringBuilder stringBuilder = new StringBuilder();
static StringBuffer stringBuffer = new StringBuffer();
public static void main(String args[]) throws InterruptedException {
T t = new T();
S s = new S();
t.start();
s.start();
Thread.sleep(50);
System.out.println("string \t\t\t" + string);
System.out.println("stringbuffer \t" + stringBuffer);
System.out.println("stringbuilder \t" + stringBuilder);
}
static class T extends Thread{
@Override
public void run(){
for(int i = 0; i < 5; i++){
for(int j = 0; j < 10; j++) string += "t";
for(int j = 0; j < 10; j++) stringBuilder.append("t");
for(int j = 0; j < 10; j++) stringBuffer.append("t");
try {
sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class S extends Thread{
@Override
public void run(){
for(int i = 0; i < 5; i++){
for(int j = 0; j < 10; j++) string += "s";
for(int j = 0; j < 10; j++) stringBuilder.append("s");
for(int j = 0; j < 10; j++) stringBuffer.append("s");
try {
sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Output
These codes end in different results.
Sometimes it prints three lines of strings, which could be the same, or not the same.
Sometimes, unexpected hollow squares apper in stringBuilder, as is shown below.
Sometimes, it throws an exception.
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:826)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:422)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at trash.PlayJava$S.run(Test.java:61)
Explanation
StringBuilder is not thread save class, while StringBuffer is. According to the source code, most of methods in StringBuffer use synchronized key word.
Lab 3 Speed Comparison
static final int a = 500000;
static String string = "";
static StringBuilder stringBuilder = new StringBuilder();
static StringBuffer stringBuffer = new StringBuffer();
public static long run(String s){
long startTime = System.currentTimeMillis();
for(int i = 0; i < a; i++) s += "s";
return System.currentTimeMillis() - startTime;
}
public static long run(StringBuilder s){
long startTime = System.currentTimeMillis();
for(int i = 0; i < a; i++) s.append("s");
return System.currentTimeMillis() - startTime;
}
public static long run(StringBuffer s){
long startTime = System.currentTimeMillis();
for(int i = 0; i < a; i++) s.append("s");
return System.currentTimeMillis() - startTime;
}
public static void main(String args[]){
System.out.println("String: \t\t" + String.valueOf(run(string)));
System.out.println("StringBuilder: \t" + String.valueOf(run(stringBuilder)));
System.out.println("StringBuffer: \t" + String.valueOf(run(stringBuffer)));
}
Output
String: 88422
StringBuilder: 12
StringBuffer: 27
Conclusion & Explanation
Generally, speed: StringBuilder > StringBuffer >> String. So if need to modify a string frequently, better to use StringBuilder (ignoring thread safety) and StringBuffer.
String is immutable, which means once it is created, it cannot be modified. So appending a String object is very time-comsuming.
Lab 4
public static void main(String args[]){
String s1 = "hello world",
s2 = "hello world",
s3 = new String("hello world");
StringBuilder strbd1 = new StringBuilder("hello world"),
strbd2 = new StringBuilder("hello world");
StringBuffer strbf1 = new StringBuffer("hello world"),
strbf2 = new StringBuffer("hello world");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(strbd1 == strbd2);
System.out.println(strbf1 == strbf2);
}
Output
true
false
false
false
Explanation
String is stored in Constant String Pool (an area in heap). In this example, s1 = "hello world" creates an instance in it. The next line, s2 has the same value. In this case, no more instance will be created. Instead, s2 becomes a reference, which refers to "hello world". So s1 and s2 point to same thing. By the way, String is immutable, and every immutable object in Java is thread safe.
Lab 5 Type Conversion
public static String stringBuffer2String(StringBuffer s){
return s.toString();
}
public static String stringBuilder2String(StringBuilder s){
return s.toString();
}
public static StringBuilder string2StringBuilder(String s){
return new StringBuilder(s);
}
public static StringBuilder stringBuffer2StringBuilder(StringBuffer s){
return new StringBuilder(s);
}
public static StringBuffer string2StringBuffer(String s){
return new StringBuffer(s);
}
public static StringBuffer stringBuilder2StringBuffer(StringBuilder s){
return new StringBuffer(s);
}