public java.lang.String addMethod();
Code:
0: ldc #16 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: ldc #17 // int 100000
8: if_icmpge 41
11: new #7 // class java/lang/StringBuilder
14: dup
15: invokespecial #8 // Method java/lang/StringBuilder.""😦)V
18: aload_1
19: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: ldc #19 // String wupx
28: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_1
35: iinc 2, 1
38: goto 5
41: aload_1
42: areturn
可以看出,第 8 行到第 38 行构成了一个循环体:在第 8 行的时候做条件判断,如果不满足循环条件,则跳转到 41 行。编译器做了一定程度的优化,在 11 行 new 了一个 StringBuilder 对象,然后再 19 行、23 行、28 行进行了三次 append() 方法的调用,不过每次循环都会重新 new 一个 StringBuilder 对象。
再来看 stringBuilderMethod() 方法的字节码:
public java.lang.String stringBuilderMethod();
Code:
0: new #7 // class java/lang/StringBuilder
3: dup
4: invokespecial #8 // Method java/lang/StringBuilder.""😦)V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: ldc #17 // int 100000
13: if_icmpge 33
16: aload_1
17: iload_2
18: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: ldc #19 // String wupx
23: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: pop
27: iinc 2, 1
30: goto 10
33: aload_1
34: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: areturn
13 行到 30 行构成了循环体,可以看出,在第4行(循环体外)就构建好了 StringBuilder 对象,然后再循环体内只进行 append() 方法的调用。
由此可以看出,在 for 循环中,使用 + 进行字符串拼接,每次都是 new 了一个 StringBuilder,然后再把 String 转成 StringBuilder,再进行 append,而频繁的新建对象不仅要耗费很多时间,还会造成内存资源的浪费。这就从字节码层面解释了为什么不建议在循环体内使用 + 去进行字符串的拼接。
接下来再来让我们看下使用 + 或者 StringBuilder 拼接字符串的原理吧。
2、使用 + 拼接字符串
================
在 Java 开发中,最简单常用的字符串拼接方法就是直接使用 + 来完成:
String boy = “wupx”;
String girl = “huxy”;
String love = boy + girl;
反编译后的内容如下:(使用的反编译工具为 jad)
String boy = “wupx”;
String girl = “huxy”;
String love = (new StringBu
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
ilder()).append(boy).append(girl).toString();
通过查看反编译以后的代码,可以发现,在字符串常量在拼接过程中,是将 String 转成了 StringBuilder 后,使用其 append() 方法进行处理的。
那么也就是说,Java中的 + 对字符串的拼接,其实现原理是使用 StringBuilder 的 append() 来实现的,使用 + 拼接字符串,其实只是 Java 提供的一个语法糖。
**3、**使用 StringBuilder 拼接字符串
==================================
StringBuilder 的 append 方法就是第二个常用的字符串拼接姿势了。
和 String 类类似,StringBuilder 类也封装了一个字符数组,定义如下:
char[] value;
与 String 不同的是,它并不是 final 的,所以是可以修改的。另外,与 String 不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
int count;
其 append() 方法源码如下:
public StringBuilder append(String str) {
super.append(str);
return this;
}
该类继承了 AbstractStringBuilder 类,看下其 append() 方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
首先判断拼接的字符串 str 是不是 null,如果是,调用 appendNull() 方法进行处理,appendNull() 方法的源码如下:
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = ‘n’;
value[c++] = ‘u’;
value[c++] = ‘l’;
value[c++] = ‘l’;
count = c;
return this;
}
如果字符串 str 不为 null,则判断拼接后的字符数组长度是否超过当前数组长度,如果超过,则调用 Arrays.copyOf() 方法进行扩容并复制,ensureCapacityInternal() 方法的源码如下:
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}