Java8中字符串连接加号“+”和“StringBuilder#append()”

前言

  在我们平时的开发过程中,使用最多的对象几乎就是String了吧,那么自然的也涉及到了字符串的拼接。在最开始的开发经验中,我们使用最多的字符串拼接估计就是使用加号+直接将两个字符串变量拼接在一起,这样固然没什么问题,但是当我们知道了如下特性之后:

String StringBuffer StringBuilder
类是否可变 不可变(final) 可变 可变
功能介绍 每次对String的操作都会在“常量池”中生成新的String对象 任何对它指向的字符串的操作都不会
产生新的对象。每个 StringBuffer
对象都有一定的缓冲区容量,字符串
大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,自动扩容
功能与 StringBuffer相同,相比少了同步锁,执行速度更快
线程安全 线程安全 线程安全 线程不安全
使用场景推荐 单次操作或者循环外操作字符串 多线程操作字符串 单线程操作字符串

  对于字符串拼接的场景我们更多的开始优先使用StringBuilder

  其实在Java8中对于String对象使用+的这种这种拼接方式,在编译之后在部分场景+java.lang.StringBuilder#append(java.lang.String)是完全一样的,但是如果想要更加灵活更加高效的达到字符串连接的目的,还是尽量使用StringBuilder,这样代码可读性也会更高,而不是把代码交给编译器转换之后进行编译,那么我们这里就通过实际代码进行分析。

“+”编译后

看看如果我们在程序中直接使用+来连接字符串的情况,用下面一个简单的例子来说明,进行两个字符串连接操作,即s3 = s1 + s2

public class Main3 {
    public static void main(String[] args) {
        String s1 = "11111";
        String s2 = "22222";
        String s3 = s1 + s2;
    }
}

接着javap -c Main3.class看一下编译后的情况,可以看到编译器其实是对+进行了转换的,转成了 StringBuilder对象来操作了,首先使用 s1 创建 StringBuilder 对象,然后用 append方法连接 s2,最后调用toString方法完成赋值给s3。

public class com.sijibao.gyl.mallservice.test.Main3 {
  public com.sijibao.gyl.mallservice.test.Main3();
    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 11111
       2: astore_1
       3: ldc           #3                  // String 22222
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: aload_1
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      17: aload_2
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: return
}

“+”与”append”等价吗

  前面可以看到+在编译器作用下都会转成 StringBuilderappend方法执行,所以如果抛开运行效率来说,它们其实本质是一样的。

  本质一样是否就能说明它们是等价的呢?或者说能否为了方便直接用+来连接字符串,剩下的事就交给编译器了?继续看个例子,在这个例子中有个 for 循环进行字符串连接操作。

public class Main {
    public static void main(String[] args) {
        String s4 = "sfsd1fds";
        for (int i = 0; i < 11; i++) {
            s4 = s4 + i;
        }
        System.out.println(s4);
    }
}
public class com.sijibao.gyl.mallservice.test.Main {
  public com.sijibao.gyl.mallservice.test.Main();//实例化方法执行
    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 sfsd1fds//从字符串常量池中加载字符串"sfsd1fds"到虚拟机栈
       2: astore_1                          //将字符串"sfsd1fds"字面量的引用赋值给s4
       3: iconst_0                          //声明int常量,即for循环中的字面量:0
       4: istore_2                          //将int型字面量0赋值给int i
       5: iload_2                           //从本地变量加载int
       6: bipush        11                  //加载byte变量11,在运行过程中数字-128~127实际是byte类型,大于127时是short类型
       8: if_icmpge     36                  //if_icmp<cond>int比较成功时分支,ge即greater or equal
      11: new           #3                  // class java/lang/StringBuilder//在堆中创建一个StringBuilder对象,并将对应的引用压入到栈顶
      14: dup                               // 复制操作数堆栈上的顶部值,并将复制的值推送到操作数堆栈上(即这时候操作数栈中,栈顶和第二条数据都是上一步new出来的对象的引用)
      15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V//调用实例方法;对超类、私有和实例初始化方法调用的特殊处理(自动弹出栈顶数据new引用,带入到实例方法中)
      18: aload_1                           //从局部变量加载引用
      19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;//调用实例方法;对超类、私有和实例初始化方法调用的特殊处理
      22: iload_2                           //从局部变量加载int
      23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;//调用实例方法;对超类、私有和实例初始化方法调用的特殊处理
      26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;//调用实例方法;对超类、私有和实例初始化方法调用的特殊处理
      29: astore_1                          //将引用存储到局部变量中
      30: iinc          2, 1                //按常量递增局部变量(i++)
      33: goto          5                   //回到代码行5重新执行循环
      36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;//从类中获取静态字段
      39: aload_1                           //从局部变量加载引用
      40: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V//调用实例方法;对超类、私有和实例初始化方法调用的特殊处理
      43: return                            //方法执行完成,返回void
}
  • aload_<n>:从局部变量加载引用到操作数栈中(入栈)
  • ldc:从运行时常量池中获取数据,然后压入虚拟机栈中(直接声明的字符串"sfsd1fds"会首先自动先放在字符串常量池中(独立于堆之外、存在于方法区))(入栈)
  • astore_<n>:将引用存储到局部变量中(出栈)
  • iconst_<i>:声明int型常量,并添加到操作数栈(入栈)
  • istore_<n>:将int存储到局部变量中(出栈)
  • iload_<n>:从局部变量加载int(入栈)
  • bipush:加载byte常量池数据到操作数栈中(在java文件转换成class文件后,实际上-128~127的int型数据会转而使用byte来存储,大于127的数据会转换成short)(bi:binary)(入栈)
  • if_icmp<cond>:int比较成功时分支,ge即greater or equal(大于或者等于,不是>=,而是>或者==都是这个指令)(icmp:int compare)
  • new:在堆中创建一个对象:在堆中开辟内存空间并创建对象,并将对应的引用压入栈顶(推堆)(入栈)
  • dup:复制操作数堆栈上的顶部值,并将复制的值推送到操作数堆栈上(入栈)
  • invokevirtual:调用实例方法(一般在后面会有具体调用的类和方法)
  • iinc:按常量递增局部变量

扩展引用:javap—跟在new指令屁股后面的dup指令

以上代码等价于这样来写:

public class Main {
    public static void main(String[] args) {
        String s4 = "sfsd1fds";
        for (int i = 0; i < 11; i++) {
            StringBuilder s4SB = new StringBuilder();
            s4SB.append(s4);
            s4SB.append(i);
            s4 = s4SB.toString();
        }
        System.out.println(s4);
    }
}
  1. 每次循环中都需要new一个StringBuilder对象
  2. 执行两次append方法
  3. 转换成String对象
  4. 虚拟机栈中的局部变量s4指向新的地址(闲置之前的字面量,使其在后续被当作垃圾而回收)

友好写法

把事情都丢给编译器是不友好的,为了能让程序执行更加高效,最好是我们自己来控制StringBuilder的实例,比如下面,只创建一个 StringBuilder实例,后面用append方法连接字符串。

public class Main2 {
    public static void main(String[] args) {
        StringBuilder s4 = new StringBuilder("1");
        for (int i = 0; i < 10; i++) {
            s4.append(i);
        }
        System.out.println(s4);
    }
}
public class com.sijibao.gyl.mallservice.test.Main2 {
  public com.sijibao.gyl.mallservice.test.Main2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/StringBuilder//在堆中创建一个StringBuilder对象,并将创建对象的引用压入栈顶
       3: dup                               // 复制一份栈顶数据压入栈中
       4: ldc           #3                  // String 1//从运行时常量池中加载数据,并压入栈中
       6: invokespecial #4                  // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V // StringBuilder的实例化方法
       9: astore_1                          // 将StringBuilder的引用存如到局部变量中
      10: iconst_0                          // 声明int类型常量0
      11: istore_2                          // 将int类型数据赋值即:int i = 0
      12: iload_2                           // 加载局部变量i到操作数栈中
      13: bipush        10                  // java.lang.Byte.ByteCache 数据10入栈
      15: if_icmpge     30                  // for循环中的逻辑比较是否满足
      18: aload_1                           // 装载变量s4到操作数栈中
      19: iload_2                           // 转载变量i到操作数栈中
      20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;//执行append方法
      23: pop                               // 弹出顶部操作数栈中的值
      24: iinc          2, 1                // i自增
      27: goto          12                  // 跳转代码行12
      30: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;获取静态变量System.out
      33: aload_1                           // 装载变量s4到操作数栈中
      34: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      37: return
}

以上结论仅针对Java8,在Java9之后的版本中其字节码比较简洁,使用的是命令invokedynamic

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • Java概述 何为编程 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。 ...
    极课编程阅读 4,768评论 0 40
  • 数据的传递 1.实参的数目、数据类型和次序必须和所调用的方法声明的形式参数列表匹配。 2.return 语句...
    仙人掌_b340阅读 267评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,532评论 28 53
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 3,770评论 0 5
  • 首先介绍下自己的背景: 我11年左右入市到现在,也差不多有4年时间,看过一些关于股票投资的书籍,对于巴菲特等股神的...
    瞎投资阅读 5,721评论 3 8