StringBuilder 的 append() 方法
“循环体内,拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 号操作符”
这句话,我们很熟悉,那你知道是为什么吗?
+
号操作符其实被 Java 在编译的时候重新解释了,换一种说法就是,+
号操作符是一种语法糖,让字符串的拼接变得更简便了。
class Demo {
public static void main(String[] args) {
String chenmo = "沉默";
String wanger = "王二";
System.out.println(chenmo + wanger);
}
}
在 Java 8 的环境下,使用 javap -c Demo.class 反编译字节码后,可以看到以下内容:
Compiled from "Demo.java"
class Demo {
Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 沉默
2: astore_1
3: ldc #3 // String 王二
5: astore_2
6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
}
看第9行,这里有一个 new 关键字,并且 class 类型为 java/lang/StringBuilder。
“这意味着**新建了一个 StringBuilder 的对象**。”
然后看标号为 17 的这行,是一个 invokevirtual 指令,用于调用对象的方法,
也就是 StringBuilder 对象的 append() 方法。
也就意味着把 chenmo 这个字符串添加到 StringBuilder 对象中了。
再往下看,标号为 21 的这行,又调用了一次 append() 方法,
意味着把 wanger 这个字符串添加到 StringBuilder 对象中了。
换成 Java 代码来表示的话,大概是这个样子:
class Demo {
public static void main(String[] args) {
String chenmo = "沉默";
String wanger = "王二";
System.out.println((new StringBuilder(String.valueOf(chenmo))).append(wanger).toString());
}
}
原来编译的时候把“+
”号操作符替换成了 StringBuilder
的 append()
方法啊。
是的,不过到了 Java 9,情况发生了一些改变,同样的代码,字节码指令完全不同了。
同样的代码,在 Java 11 的环境下,字节码指令是这样的:
Compiled from "Demo.java"
public class com.itwanger.thirtyseven.Demo {
public com.itwanger.thirtyseven.Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush 10
8: if_icmpge 41
11: new #3 // class java/lang/String
14: dup
15: ldc #4 // String 沉默
17: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V
20: astore_3
21: ldc #6 // String 王二
23: astore 4
25: aload_1
26: aload_3
27: aload 4
29: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
34: astore_1
35: iinc 2, 1
38: goto 5
41: return
}
看标号为 29 的这行,字节码指令为 invokedynamic,
该指令允许由应用级的代码来决定方法解析,所谓的应用级的代码其实是一个方法——被称为引导方法(Bootstrap Method),简称 BSM,
BSM 会返回一个 CallSite(调用点) 对象,这个对象就和 invokedynamic 指令链接在一起。
以后再执行这条 invokedynamic 指令时就不会创建新的 CallSite 对象。
CallSite 其实就是一个 MethodHandle(方法句柄)的 holder,
指向一个调用点真正执行的方法——此时就是 StringConcatFactory.makeConcatWithConstants() 方法。
好吧,总之就是 Java 9 以后,JDK 用了另外一种方法来动态解释 + 号操作符
,具体的实现方式在字节码指令层面已经看不到了
循环体内,拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 号操作符。原因就在于循环体内如果用 + 号操作符的话,就会产生大量的 StringBuilder 对象,不仅占用了更多的内存空间,还会让 Java 虚拟机不停的进行垃圾回收,从而降低了程序的性能。
更好的写法就是在循环的外部新建一个 StringBuilder 对象,然后使用 append() 方法将循环体内的字符串添加进来:
class Demo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < 10; i++) {
String chenmo = "沉默";
String wanger = "王二";
sb.append(chenmo);
sb.append(wanger);
}
System.out.println(sb);
}
}
来做个小测试。
第一个,for 循环中使用”+”号操作符。
String result = "";
for (int i = 0; i < 100000; i++) {
result += "六六六";
}
第二个,for 循环外部新建 StringBuilder,循环体内使用 append() 方法。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("六六六");
}
package com.study;
/** * @Description 测试字符串拼接+,StringBuilder * @Classname JoinStringsDemo * @Date 2021/8/22 16:11 * @Created by 折腾的小飞 */
public class JoinStringsDemo {
public static void main(String[] args) {
new Thread(()->{
long startTime=System.currentTimeMillis();
String result = "";
for (int i = 0; i < 100000; i++) {
result += "六六六";
}
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
}).start();
new Thread(()->{
long startTime=System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("六六六");
}
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
}).start();
}
}
使用+拼接字符串执行时间是 4892 毫秒,使用StringBuffer只用了不到 11毫秒,差距也太大了吧!
来看一下 StringBuilder 类的 append() 方法的源码吧!
public StringBuilder append(String str) {
super.append(str);
return this;
}
这 3 行代码其实没啥看的。我们来看父类 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;
}
1)判断拼接的字符串是不是 null,如果是,当做字符串“null”来处理。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;
}
2)获取字符串的长度。
3)ensureCapacityInternal()
方法的源码如下:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
由于字符串内部是用数组实现的,所以需要先判断拼接后的字符数组长度是否超过当前数组的长度,如果超过,先对数组进行扩容,然后把原有的值复制到新的数组中。
4)将拼接的字符串 str 复制到目标数组 value 中。
str.getChars(0, len, value, count)
5)更新数组的长度 count。
说到 StringBuilder 就必须得提一嘴 StringBuffer,两者就像是孪生双胞胎,该有的都有,只不过大哥 StringBuffer 因为多呼吸两口新鲜空气,所以是线程安全的。”我说,“它里面的方法基本上都加了 synchronized 关键字来做同步。
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
除了可以使用 + 号操作符,StringBuilder 和 StringBuilder 的 append() 方法,还有其他的字符串拼接方法吗?
String 类的 concat() 方法
String chenmo = "沉默";
String wanger = "王二";
System.out.println(chenmo.concat(wanger));
可以来看一下 concat()
方法的源码。
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
1)如果拼接的字符串的长度为 0,那么返回拼接前的字符串。
2)将原字符串的字符数组 value 复制到变量 buf 数组中。
3)把拼接的字符串 str 复制到字符数组 buf 中,并返回新的字符串对象。
和 + 号操作符相比,concat()
方法在遇到字符串为 null 的时候,会抛出 NullPointerException
,而“+”号操作符会把 null 当做是“null”字符串来处理。
如果拼接的字符串是一个空字符串(""),那么 concat 的效率要更高一点,
毕竟不需要 new StringBuilder 对象。
如果拼接的字符串非常多,concat() 的效率就会下降,
因为创建的字符串对象越来越多。
String 类有一个静态方法 join()
String chenmo = "沉默";
String wanger = "王二";
String cmower = String.join("", chenmo, wanger);
System.out.println(cmower);
第一个参数为字符串连接符
String message = String.join("-", "王二", "太特么", "有趣了");
输出结果为:王二-太特么-有趣了
。
来看一下 join 方法的源码:
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
里面新建了一个叫 StringJoiner
的对象,然后通过 for-each 循环把可变参数添加了进来,最后调用 toString() 方法返回 String。
org.apache.commons.lang3.StringUtils的join() 方法
实际的工作中,org.apache.commons.lang3.StringUtils
的 join()
方法也经常用来进行字符串拼接。
String chenmo = "沉默";
String wanger = "王二";
StringUtils.join(chenmo, wanger);
该方法不用担心 NullPointerException。
StringUtils.join(null) = null
StringUtils.join([]) = ""
StringUtils.join([null]) = ""
StringUtils.join(["a", "b", "c"]) = "abc"
StringUtils.join([null, "", "a"]) = "a"
来看一下源码:
public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
if (array == null) {
return null;
}
if (separator == null) {
separator = EMPTY;
}
final StringBuilder buf = new StringBuilder(noOfItems * 16);
for (int i = startIndex; i < endIndex; i++) {
if (i > startIndex) {
buf.append(separator);
}
if (array[i] != null) {
buf.append(array[i]);
}
}
return buf.toString();
}
内部使用的仍然是 StringBuilder
。
今天的文章java如何拼接字符串数字_字符串拼接的几种方式分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/78016.html