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_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: ldc           #2                  // String 沉默2: astore_13: ldc           #3                  // String 王二5: astore_26: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;9: new           #5                  // class java/lang/StringBuilder12: dup13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V16: aload_117: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: aload_221: 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;)V30: 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());}
}

原来编译的时候把“+”号操作符替换成了 StringBuilderappend() 方法啊。

是的,不过到了 Java 9,情况发生了一些改变,同样的代码,字节码指令完全不同了。
同样的代码,在 Java 11 的环境下,字节码指令是这样的:

Compiled from "Demo.java"
public class com.itwanger.thirtyseven.Demo {public com.itwanger.thirtyseven.Demo();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: ldc           #2                  // String2: astore_13: iconst_04: istore_25: iload_26: bipush        108: if_icmpge     4111: new           #3                  // class java/lang/String14: dup15: ldc           #4                  // String 沉默17: invokespecial #5                  // Method java/lang/String."<init>":(Ljava/lang/String;)V20: astore_321: ldc           #6                  // String 王二23: astore        425: aload_126: aload_327: aload         429: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;34: astore_135: iinc          2, 138: goto          541: 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 codeif (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.StringUtilsjoin() 方法也经常用来进行字符串拼接。

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如何拼接字符串相关推荐

  1. java用 拼接字符串的原理_Java String 拼接字符串原理详解

    首先来一道思考题: String str1 = "111111"; String str2 = "222222"; String str = str1 + st ...

  2. java求拼接后的字符串长度,java如何拼接字符串

    java 动态拼接字符串,Java 字符串拼接效率分析及最佳实践,java字符串拼接,java如何拼接字符串 在JAVA 中拼接两个字符串的最简便的方式就是使用操作符"+"了.如果 ...

  3. java逗号拼接字符串并且截断最后一个逗号(StrBuilder和substring)

    java逗号拼接字符串并且截断最后一个逗号 List<String> strings = new ArrayList<>();strings.add("aa" ...

  4. Java json拼接字符串_JSONObject与java字符串拼接json的区别

    背景 事情的起因是这样的.最近正在调试Socket.io的Android端接口.自己找到的Socket.io的Android的官方资料只是简单的字符串传递,而后台的接口则是一次性传递多组键值对参数,考 ...

  5. java 加单引号_【沫沫金】Java逗号拼接字符串增加单引号

    背景 页面提供逗号拼接的字符串,可作为数据库查询in的条件. a,b 问题 数据库针对字符串的in条件,要求增加单引号 xx in ('a','b') 需求 页面的逗号拼接字符串直接转换成数据库要求格 ...

  6. java 循环拼接字符串用分号隔开_Java 8中字符串拼接新姿势:StringJoiner

    在为什么阿里巴巴不建议在for循环中使用"+"进行字符串拼接一文中,我们介绍了几种Java中字符串拼接的方式,以及优缺点.其中还有一个重要的拼接方式我没有介绍,那就是Java 8中 ...

  7. java通过+拼接字符串导致的无效SQL,三目运算符与+运算符结合使用时需要注意了

    调试代码的过程中遇到一个比较尴尬的问题,java代码中先进行sql拼接,然后再执行拼接后的sql,即一个又臭又长的字符串.设计到sql拼接的情况,我个人比较喜欢用StringBuilder拼接,毕竟使 ...

  8. java list 拼接 字符串数组_把数组所有元素排序,并按照“参数=参数值”的模式用“”字符拼接成字符串...

    /** * 把数组所有元素排序,并按照"参数=参数值"的模式用"&"字符拼接成字符串 * @param params 需要排序并参与字符拼接的参数组 * ...

  9. java sql拼接字符串_SQL中字符串拼接

    1. 概述 在SQL语句中经常需要进行字符串拼接,以sqlserver,oracle,mysql三种数据库为例,因为这三种数据库具有代表性. sqlserver: select '123'+'456' ...

最新文章

  1. MATLAB012b与vs2012混合编程——配置vs2012工作环境
  2. 测开之路十五:构造函数、析构函数
  3. 机器学习系列(2)_从初等数学视角解读逻辑回归
  4. C++之stdafx.h的用法说明
  5. [css] flex布局的缺点有哪些?(除兼容性外)
  6. Eclipse快捷键生成语句
  7. 新华三社招流程_【面试经验|校招】新华三/产品经理
  8. bzoj 1042: [HAOI2008]硬币购物(dp+容斥)
  9. codeforces 463A Caisa and Sugar 解题报告
  10. Android 网络学习之获取服务器的图片
  11. windows怎么远程关linux系统,从windows远程关闭linux、windows系统.doc
  12. 捕鱼达人python游戏项目,少儿编程体验课程项目,源码免费分享,内置详细注释,可更改游戏参数;关注获取更多资源
  13. linux u盘安装win8,u盘启动大师安装深度技术win8.1系统教程
  14. AirPlay to Mac 如何工作以及使用它需要什么?
  15. 4.6.2 IPv6的地址
  16. 从苏宁电器到卡巴斯基第17篇:曲折考研路(上)
  17. git 找回删除的文件
  18. 听别人的故事探索属于自己的方法
  19. linux网络存储服务器选题意义,linux网络存储服务器iscsi详细介绍分析.doc
  20. 《青春有你》新增公益任务 张艺兴蔡依林等当导师

热门文章

  1. ML基石_14_Regularization
  2. 三代组装软件canu学习笔记
  3. html 链接 pdf,简单的HTML DOM只解析名称和含有PDF链接链接
  4. TensorFlow基础3-机器学习基础知识(解析法实现一元线性回归、多元线性回归)
  5. python笔记3(numpy数组)
  6. 简述控制反转ioc_阅读Spring源码:IOC控制反转前的处理
  7. tf.keras.layers.Flatten() 示例
  8. 3dsmax 长动画导入 three.js 转变成 多个动画
  9. python 导入自定义模块
  10. 小学生计算机课堂实践的重要性,多媒体在小学教学中的重要性