为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

简介: 之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下:那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率如何吧(JDK版本为 jdk1.8.0_201)。

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下:

阿里巴巴Java开发手册

那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率如何吧(JDK版本为 jdk1.8.0_201)。

public class StringConcatDemo {
    public static void main(String[] args) {
        long s1 = System.currentTimeMillis();
        new StringConcatDemo().addMethod();
        System.out.println("使用 + 拼接:" + (System.currentTimeMillis() - s1));

        s1 = System.currentTimeMillis();
        new StringConcatDemo().stringBuilderMethod();
        System.out.println("使用 StringBuilder 拼接:" + (System.currentTimeMillis() - s1));
    }

    public String addMethod() {
        String result = "";
        for (int i = 0; i < 100000; i++) {
            result += (i + "wupx");
        }
        return result;
    }

    public String stringBuilderMethod() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < 100000; i++) {
            result.append(i).append("wupx");
        }
        return result.toString();
    }
}

执行结果如下:

使用 + 拼接:29282
使用 StringBuilder 拼接:4

为什么这两种方法的时间会差这么多呢?接下来让我们一起进一步研究。

为什么 StringBuilder 比 + 快这么多?

从字节码层面来看下,为什么循环体中字符串拼接 StringBuilder 比 + 快这么多?

使用 javac StringConcatDemo.java 命令编译源文件,使用 javap -c StringConcatDemo 命令查看字节码文件的内容。

其中 addMethod() 方法的字节码如下:

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."<init>":()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."<init>":()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 拼接字符串的原理吧。

使用 + 拼接字符串

在 Java 开发中,最简单常用的字符串拼接方法就是直接使用 + 来完成:

String boy = "wupx";
String girl = "huxy";
String love = boy + girl;

反编译后的内容如下:(使用的反编译工具为 jad)

String boy = "wupx";
String girl = "huxy";
String love = (new StringBuilder()).append(boy).append(girl).toString();

通过查看反编译以后的代码,可以发现,在字符串常量在拼接过程中,是将 String 转成了 StringBuilder 后,使用其 append() 方法进行处理的。

那么也就是说,Java中的 + 对字符串的拼接,其实现原理是使用 StringBuilder 的 append() 来实现的,使用 + 拼接字符串,其实只是 Java 提供的一个语法糖。

使用 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));
    }
}

最后,将拼接的字符串 str 复制到目标数组 value 中。

str.getChars(0, len, value, count);

总结

本文针对《阿里巴巴Java开发手册》中的循环体中拼接字符串建议出发,从字节码层面,来解释为什么 StringBuilder 比 + 快,还分别介绍了字符串拼接中 + 和 StringBuilder 的原理,因此在循环体拼接字符串时,应该使用 StringBuilder 的 append() 去完成拼接。

参考

《Java开发手册》华山版

《Effective Java(第二版)》

《Java编程思想》

目录
相关文章
|
3月前
|
Java 关系型数据库 MySQL
兴奋!阿里巴巴首推“Java进阶必备宝典”,理论到实战,一键搞定
作为一名Java方向的程序员,打好夯实的基础是非常重要的,现在大厂面试对于程序员基础知识的掌握考察也越来越严格,虽然说现在技术更新比较快,但基础扎实才能够更深入的去理解每一个知识技术点。
|
3月前
|
Java
电子书阅读分享《Java开发手册(嵩山版)》
电子书阅读分享《Java开发手册(嵩山版)》
625 0
|
20天前
|
存储 安全 Java
Java中实现高效的字符串拼接技术
【4月更文挑战第6天】在Java编程中,字符串拼接是一个常见的操作。然而,由于字符串的不可变性,频繁的拼接操作可能会导致性能问题。本文将探讨Java中实现高效字符串拼接的技术,包括使用StringBuilder类、StringBuffer类以及Java 8中的StringJoiner类。通过对比这些技术的优缺点,我们将为您提供在不同场景下选择合适的字符串拼接方法的建议。
|
1月前
|
存储 算法 Java
超全面!阿里巴巴最新发布23年秋招200道Java面试题(含答案)
马上过34岁生日了,和大家聊聊最近的情况 半年前还在迷茫该学什么,怎样才能走出现在的困境,半年后已经成功上岸阿里,感谢在这期间帮助我的每一个人。 面试中总结了200道经典的Java面试题,里面包含面试要回答的知识重点,并且我根据知识类型进行了分类,可以说非常全面了~ 因为篇幅原因,大部分的内容就不给大家一一展示了,需要获取的小伙伴可以直接点击此处取到! Java平台相关 1、JDK、JRE、JVM 分别是什么关系? 2、为什么 Java 被称作是“平台无关的编程语言”? 3、Java 和 C++ 的区别? 4、什么是字节码?采用字节码的最大好处是什么? 5、Java运行的过程? 6、
92 4
|
1月前
|
存储 算法 Java
超全面!阿里巴巴最新发布23年秋招200道Java面试题(含答案)
马上过34岁生日了,和大家聊聊最近的情况 半年前还在迷茫该学什么,怎样才能走出现在的困境,半年后已经成功上岸阿里,感谢在这期间帮助我的每一个人。 面试中总结了200道经典的Java面试题,里面包含面试要回答的知识重点,并且我根据知识类型进行了分类,可以说非常全面了~ 因为篇幅原因,大部分的内容就不给大家一一展示了,需要获取的小伙伴可以直接点击此处取到! Java平台相关 1、JDK、JRE、JVM 分别是什么关系? 2、为什么 Java 被称作是“平台无关的编程语言”? 3、Java 和 C++ 的区别? 4、什么是字节码?采用字节码的最大好处是什么? 5、Java运行的过程? 6、
|
2月前
|
Java
电子书阅读分享《Java开发手册(泰山版)》
电子书阅读分享《Java开发手册(泰山版)》
1000 1
电子书阅读分享《Java开发手册(泰山版)》
|
3月前
|
算法 C++ Java
Java每日一练(20230422) 拼接最大数、Z字形变换、跳跃游戏
Java每日一练(20230422) 拼接最大数、Z字形变换、跳跃游戏
32 0
Java每日一练(20230422) 拼接最大数、Z字形变换、跳跃游戏
|
3月前
|
设计模式 算法 NoSQL
阿里巴巴官方上线!号称国内Java八股文天花板(终极版)首次开源
真正有意义的就业与跳槽,是要进入到一个有绝对潜力的行业或者薪资能实现爆炸式增长的。这件事不容易,但也没有想象的遥不可及,现在大环境不好,机会也不如以前多,除了让自身技术能力过硬,面试更是要好好准备! 如何准备? 除了平时的技术积累与沉淀之外,剩下的就只能背八股了(虽然工作用不到,但面试就是要问,不背是不行的)。
82 0
|
3月前
|
Java 程序员 数据库连接
阿里巴巴大神发布的Java零基础笔记,实战教程多到手软,跪了
现值金九银十之际,是面试高峰季,很多学校开始校招,也是跳槽转行的最佳时机。根据数据显示,程序员是金九银十里最热门的行业,也是需求量最大的行业,但是程序员是个门槛低,但金字塔顶峰比较高的行业,意味着你的付出要比别人多才能拔尖。
|
3月前
|
架构师 NoSQL Java
阿里巴巴新产“Java架构核心宝典”,全是流行技术,限时开放
什么是架构师?对于程序员来说,聊架构是一个永不过时的话题。实际上,每一家公司都有自己对架构师不同的定位,因为不同的公司,所处的阶段、业务模式以及应用场景都不一样,因此对架构师的要求不一样,所以定位也就不同。