String, StringBuffer, StringBuilder

Even if the StringBuilder  was around for a while now, many people still use StringBuffer  in single threaded applications. The main difference is stated in the StringBuffer  comments:
「As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, {StringBuilder}. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization.」

So StringBuilder is supposed to be faster. But how much faster? Let’s see… java

I create a small program to stress test StringBuffer vs.StringBuilder. As a reference I also do straight Stringconcatenation. In a big loop I just add one character to aString/StringBuffer/StringBuilder. I also play with the initial memory allocation for the StringBuilder and StringBuffercases. Here is the code: app

01. package com.littletutorials.tips;
02.  
03. public class ConcatPerf {
04. private static final int ITERATIONS =100000;
05. private static final int BUFFSIZE = 16;
06.  
07. private void concatStrAdd() {
08. System.out.print("concatStrAdd   -> ");
09. long startTime = System.currentTimeMillis();
10. String concat = "";
11. for (int i = 0; i < ITERATIONS; i++) {
12. concat += i % 10;
13. }
14. long endTime = System.currentTimeMillis();
15. System.out.print("length: " + concat.length());
16. System.out.println(" time: " + (endTime - startTime));
17. }
18.  
19. private void concatStrBuff() {
20. System.out.print("concatStrBuff  -> ");
21. long startTime = System.currentTimeMillis();
22. StringBuffer concat = newStringBuffer(BUFFSIZE);
23. for (int i = 0; i < ITERATIONS; i++) {
24. concat.append(i % 10);
25. }
26. long endTime = System.currentTimeMillis();
27. System.out.print("length: " + concat.length());
28. System.out.println(" time: " + (endTime - startTime));
29. }
30.  
31. private void concatStrBuild() {
32. System.out.print("concatStrBuild -> ");
33. long startTime = System.currentTimeMillis();
34. StringBuilder concat = newStringBuilder(BUFFSIZE);
35. for (int i = 0; i < ITERATIONS; i++) {
36. concat.append(i % 10);
37. }
38. long endTime = System.currentTimeMillis();
39. System.out.print("length: " + concat.length());
40. System.out.println(" time: " + (endTime - startTime));
41. }
42.  
43. public static void main(String[] args) {
44. ConcatPerf st = new ConcatPerf();
45. System.out.println("Iterations: " + ITERATIONS);
46. System.out.println("Buffer    : " + BUFFSIZE);
47.  
48. st.concatStrBuff();
49. st.concatStrBuild();
50. st.concatStrAdd();
51. }
52. }

This example allows me to play with the number of iterations, the initial buffer size (not for String concatenation) and which tests I run. Nothing smart, just commenting what I don’t want to run. oop

The numbers are not really important but the percentage difference is. ui

The first run with all three tests and the default capacity: this


Iterations: 100000
Capacity : 16
concatStrBuff -> length: 100000 time: 16
concatStrBuild -> length: 100000 time: 15
concatStrAdd -> length: 100000 time: 10437 spa

This makes it clear I should never use plain String concatenation in big loops. Of course if you just concatenate a few strings once in a while this doesn’t matter. The explanation is clear from the bytecode generated for the loop in the concatStrAdd() method (fragment): code

01. L3
02. LINENUMBER 15 L3
03. ICONST_0
04. ISTORE 4
05. L4
06. GOTO L5
07. L6
08. LINENUMBER 17 L6
09. FRAME APPEND [J java/lang/String I]
10. NEW java/lang/StringBuilder
11. DUP
12. ALOAD 3
13. INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
14. INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
15. ILOAD 4
16. BIPUSH 10
17. IREM
18. INVOKEVIRTUAL java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder;
19. INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
20. ASTORE 3
21. L7
22. LINENUMBER 15 L7
23. IINC 4 1
24. L5
25. FRAME SAME
26. ILOAD 4
27. LDC 100000000
28. IF_ICMPLT L6
29. L8

The compiler uses a StringBuilder to concatenate Strings but unfortunately it creates one instance of it at every iteration. Compare it with the code generated for the loop in the concatStrBuild()method (fragment): orm

01. L3
02. LINENUMBER 43 L3
03. ICONST_0
04. ISTORE 4
05. L4
06. GOTO L5
07. L6
08. LINENUMBER 45 L6
09. FRAME APPEND [J java/lang/StringBuilder I]
10. ALOAD 3
11. ILOAD 4
12. BIPUSH 10
13. IREM
14. INVOKEVIRTUAL java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder;
15. POP
16. L7
17. LINENUMBER 43 L7
18. IINC 4 1
19. L5
20. FRAME SAME
21. ILOAD 4
22. LDC 100000000
23. IF_ICMPLT L6
24. L8

Increasing the initial capacity for StringBuffer andStringBuilder doesn’t make much difference. Clearly with only 100,000 iterations the numbers for StringBuffer andStringBuilder are just noise. three


Iterations: 100000
Capacity : 100000
concatStrBuff -> length: 100000 time: 15
concatStrBuild -> length: 100000 time: 16
concatStrAdd -> length: 100000 time: 10594 ip

Let’s crank it up… but without the String concatenation test because it will never finish.


Iterations: 100000000
Capacity : 16
concatStrBuff -> length: 100000000 time: 15142
concatStrBuild -> length: 100000000 time: 10891

Now it is pretty clear StringBuilder is much faster because it avoids synchronization.


Iterations: 100000000
Capacity : 100000000
concatStrBuff -> length: 100000000 time: 14220
concatStrBuild -> length: 100000000 time: 10611

Allocating all the memory at once does make some difference but nothing really spectacular. But if we look at how the memory allocation peaks we get some more food for thought:

Memory

Bump 1: StringBuffer with buffer 100000000
Bump 2: StringBuffer with buffer 16
Bump 3: StringBuilder with buffer 100000000
Bump 4: StringBuilder with buffer 16

So StringBuilder is faster by a good percentage (34% on my machine in this case) but remember that it is not thread safe.

相關文章
相關標籤/搜索