字符串拼接操作在底层是如何操作的?

本文探讨了Java中字符串拼接的不同方式及其内存表现。通过实例代码和编译后字节码分析,展示了直接字符串连接、使用StringBuilder以及final变量拼接的原理。讲解了为何s1==s2为true,s2==s3为false,以及s4==s5为true的原因,揭示了Java字符串优化和内存管理的细节。
摘要由CSDN通过智能技术生成

首先看下在日常开发中我们操作字符串拼接的集中方式:

	public static void main(String[] args) {
		String s1 = "a"+"b"+"c";
		String s2 = "abc";
		
		String s3 = s2+"";
		
		final String s4 = "abc";
		String s5 = s4+"";
		
		System.out.println("s1==s2:" + (s1 == s2)); //true
		System.out.println("s2==s3:"+ (s2 == s3)); //false
		System.out.println("s4==s5:"+ (s4 == s5)); //true
	}

为什么不同的方式得出的结构不一样呢?我们先来看看这段代码编译后的结果:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=6, args_size=1
         0: ldc           #16                 // String abc
         2: astore_1
         3: ldc           #16                 // String abc
         5: astore_2
         6: new           #18                 // class java/lang/StringBuilder
         9: dup
        10: aload_2
        11: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        14: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        17: invokevirtual #29                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        20: astore_3
        21: ldc           #16                 // String abc
        23: astore        4
        25: ldc           #16                 // String abc
        27: astore        5
        29: getstatic     #33                 // Field java/lang/System.out:Ljava/io/PrintStream;
        32: new           #18                 // class java/lang/StringBuilder
        35: dup
        36: ldc           #39                 // String s1==s2:
        38: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        41: aload_1
        42: aload_2
        43: if_acmpne     50
        46: iconst_1
        47: goto          51
        50: iconst_0
        51: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
        54: invokevirtual #29                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        57: invokevirtual #45                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: getstatic     #33                 // Field java/lang/System.out:Ljava/io/PrintStream;
        63: new           #18                 // class java/lang/StringBuilder
        66: dup
        67: ldc           #50                 // String s2==s3:
        69: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        72: aload_2
        73: aload_3
        74: if_acmpne     81
        77: iconst_1
        78: goto          82
        81: iconst_0
        82: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
        85: invokevirtual #29                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        88: invokevirtual #45                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        91: getstatic     #33                 // Field java/lang/System.out:Ljava/io/PrintStream;
        94: new           #18                 // class java/lang/StringBuilder
        97: dup
        98: ldc           #52                 // String s4==s5:
       100: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       103: ldc           #16                 // String abc
       105: aload         5
       107: if_acmpne     114
       110: iconst_1
       111: goto          115
       114: iconst_0
       115: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
       118: invokevirtual #29                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       121: invokevirtual #45                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       124: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 8: 6
        line 10: 21
        line 11: 25
        line 13: 29
        line 14: 60
        line 15: 91
        line 16: 124
注意这些行号,与编译后的 LineNumberTable有关系

在这里插入图片描述

s1 == s2为true

首先看编译后的第一个指令:

0: ldc           #16                 // String abc

通过LineNumberTable与代码的对应关系可以看出

line 5: 0

第0个指令对应我们代码的第5行,也就是

String s1 = "a"+"b"+"c";

可以看出编译时 “a”+“b”+"c"直接编译成了“abc”,所以s1 == s2为true。

s2 == s3为false

s3在代码的第8行,通过LineNumberTable查看对应的是编译后的6,编译后的处理方式是:

6: new           #18                 // class java/lang/StringBuilder

可以看到是通过new StringBuilder产生的对象(如果拼接的不是空字符串,会调用append方法),s2放在字符串常量池,s3是通过new出来的放在堆空间中(虽然常量池也在堆空间内,但是s3并不是放在堆中的常量池里),所有s2 == s3为false。

关于字符串常量池等一些其它问题可以查看JVM虚拟机运行时数据区のStringTable 字符串常量池

s4 == s5为true

通过查看编译后文件,看出s5再编译中直接赋值了,由此可以看出有final修饰过的变量(叫做常量),在编译期就已经替换为常量了。所以s4 == s5为true。

由此可以得出
  1. 直接有字符串拼接,或者常量(由final修饰)直接拼接,在编译就已经优化为最终的字符串
  2. 有变量与变量,变量与字符串拼接的在编译期会编译为StringBuilder来实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦里藍天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值