> 字符串拼接一般使用“+”,但是“+”不能满足大批量数据的处理,Java中有以下五种方法处理字符串拼接,各有优缺点,程序开发应选择合适的方法实现。
-
加号 “+”
-
String contact() 方法
-
StringUtils.join() 方法
-
StringBuffer append() 方法
-
StringBuilder append() 方法
ps:org.apache.commons.lang.StringUtils类是用于操作Java.lang.String类的
> 字符串拼接一般使用“+”,但是“+”不能满足大批量数据的处理,Java中有以下五种方法处理字符串拼接,各有优缺点,程序开发应选择合适的方法实现。
-
方法1 加号 “+” 拼接 和 方法2 String contact() 方法 适用于小数据量的操作,代码简洁方便,加号“+” 更符合我们的编码和阅读习惯;
-
方法3 StringUtils.join() 方法 适用于将ArrayList转换成字符串,就算90万条数据也只需68ms,可以省掉循环读取ArrayList的代码;
-
方法4 StringBuffer append() 方法 和 方法5 StringBuilder append() 方法 其实他们的本质是一样的,都是继承自AbstractStringBuilder,效率最高,大批量的数据处理最好选择这两种方法。
-
方法1 加号 “+” 拼接 和 方法2 String contact() 方法 的时间和空间成本都很高(分析在本文末尾),不能用来做批量数据的处理。
>然后就是最关心的使用方法了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 1import java.util.ArrayList;
2import java.util.List;
3
4import org.apache.commons.lang3.StringUtils;
5
6public class TestString {
7
8 private static final int max = 100;
9
10 public void testPlus() {
11 System.out.println(">>> testPlus() <<<");
12
13 String str = "";
14
15 long start = System.currentTimeMillis();
16
17 for (int i = 0; i < max; i++) {
18 str = str + "a";
19 }
20
21 long end = System.currentTimeMillis();
22
23 long cost = end - start;
24
25 System.out.println(" {str + \"a\"} cost=" + cost + " ms");
26 }
27
28 public void testConcat() {
29 System.out.println(">>> testConcat() <<<");
30
31 String str = "";
32
33 long start = System.currentTimeMillis();
34
35 for (int i = 0; i < max; i++) {
36 str = str.concat("a");
37 }
38
39 long end = System.currentTimeMillis();
40
41 long cost = end - start;
42
43 System.out.println(" {str.concat(\"a\")} cost=" + cost + " ms");
44 }
45
46 public void testJoin() {
47 System.out.println(">>> testJoin() <<<");
48
49 long start = System.currentTimeMillis();
50
51 List<String> list = new ArrayList<String>();
52
53 for (int i = 0; i < max; i++) {
54 list.add("a");
55 }
56
57 long end1 = System.currentTimeMillis();
58 long cost1 = end1 - start;
59
60 StringUtils.join(list, "");
61
62 long end = System.currentTimeMillis();
63 long cost = end - end1;
64
65 System.out.println(" {list.add(\"a\")} cost1=" + cost1 + " ms");
66 System.out.println(" {StringUtils.join(list, \"\")} cost=" + cost
67 + " ms");
68 }
69
70 public void testStringBuffer() {
71 System.out.println(">>> testStringBuffer() <<<");
72
73 long start = System.currentTimeMillis();
74
75 StringBuffer strBuffer = new StringBuffer();
76
77 for (int i = 0; i < max; i++) {
78 strBuffer.append("a");
79 }
80 strBuffer.toString();
81
82 long end = System.currentTimeMillis();
83
84 long cost = end - start;
85
86 System.out.println(" {strBuffer.append(\"a\")} cost=" + cost + " ms");
87 }
88
89 public void testStringBuilder() {
90 System.out.println(">>> testStringBuilder() <<<");
91
92 long start = System.currentTimeMillis();
93
94 StringBuilder strBuilder = new StringBuilder();
95
96 for (int i = 0; i < max; i++) {
97 strBuilder.append("a");
98 }
99 strBuilder.toString();
100
101 long end = System.currentTimeMillis();
102
103 long cost = end - start;
104
105 System.out
106 .println(" {strBuilder.append(\"a\")} cost=" + cost + " ms");
107 }
108}
109
1
2 1**> 查看源代码,以及简单分析**
2
String contact 和 StringBuffer,StringBuilder 的源代码都可以在Java库里找到,有空可以研究研究。
-
其实每次调用contact()方法就是一次数组的拷贝,虽然在内存中是处理都是原子性操作,速度非常快,但是,最后的return语句会创建一个新String对象,限制了concat方法的速度。
1
2
3
4
5
6
7
8
9
10
11 1public String concat(String str) {
2 int otherLen = str.length();
3 if (otherLen == 0) {
4 return this;
5 }
6 int len = value.length;
7 char buf[] = Arrays.copyOf(value, len + otherLen);
8 str.getChars(buf, len);
9 return new String(buf, true);
10 }
11
2. StringBuffer 和 StringBuilder 的append方法都继承自AbstractStringBuilder,整个逻辑都只做字符数组的加长,拷 贝, 到最后也不会创建新的String对象,所以速度很快,完成拼接处理后在程序中用strBuffer.toString()来得到最终的字符串。
1
2
3
4
5
6
7
8
9 1public AbstractStringBuilder append(String str) {
2 if (str == null) str = "null";
3 int len = str.length();
4 ensureCapacityInternal(count + len);
5 str.getChars(0, len, value, count);
6 count += len;
7 return this;
8 }
9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1 private void ensureCapacityInternal(int minimumCapacity) {
2 // overflow-conscious code
3 if (minimumCapacity - value.length > 0)
4 expandCapacity(minimumCapacity);
5 }
6
7 /**
8 * This implements the expansion semantics of ensureCapacity with no
9 * size check or synchronization.
10 */
11 void expandCapacity(int minimumCapacity) {
12 int newCapacity = value.length * 2 + 2;
13 if (newCapacity - minimumCapacity < 0)
14 newCapacity = minimumCapacity;
15 if (newCapacity < 0) {
16 if (minimumCapacity < 0) // overflow
17 throw new OutOfMemoryError();
18 newCapacity = Integer.MAX_VALUE;
19 }
20 value = Arrays.copyOf(value, newCapacity);
21 }
22
- 字符串的加号“+” 方法, 虽然编译器对其做了优化,使用StringBuilder的append方法进行追加,但是每循环一次都会创建一个StringBuilder对象,且都会调用toString方法转换成字符串,所以开销很大。
如JVM对于“+”是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
注:执行一次字符串“+”,相当于 str = new StringBuilder(str).append("a").toString();
- 常说拿空间换时间,反过来是不是拿时间换到了空间呢,但是在这里,其实时间是消耗在了重复的不必要的工作上(生成新的对象,toString方法),所以对大批量数据做处理时,加号“+” 和 contact 方法绝对不能用,时间和空间成本都很高。