从字节码(ByteCode)角度理解String的连接 - 简书

首先来看一道题。

题目描述

问下面两种赋值方式有何区别?

public class Demo {
    public static void main(String[] args) {

        String s = "1" + "2" + "3";

        String s1 = "1";
        String s2 = s1 + "2";
        String s3 = s2 + "3";

        System.out.println(s);
        System.out.println(s3);
    }
}


分析解答

从表面其实看不出什么,我们可以通过Class文件反编译成的字节码(Byte Code)来分析。

如果你在使用IDEA,请先在IDEA中安装ASMified Bytecode Online插件,安装详细教程->推荐几个可以提高程序员生存技能的效率软件,如果是其他集成环境,请自行Google安装插件教程。

ASMified Bytecode Online插件作用:用于将Class文件反编译成字节码(Byte Code)形式。将上面代码在IDEA中运行后,生成的字节码(Byte Code)如下图所示:

...
public static void main(String[] a) {
    ldc "123"
    //astore 1
    ldc "1"
    //astore 2
    _new 'java/lang/StringBuilder'
    //dup
    //INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    //aload 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ldc "2"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    //astore 3
    _new 'java/lang/StringBuilder'
    //dup
    //INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    //aload 3
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ldc "3"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ....
}

看不懂,没关系!你只需要知道几个指令就能理解了。根据《深入理解Java虚拟机(第二版)》---周志明著的第六章知识可知:

  • ldc指令:将一个常量加载到操作数栈。
  • _new指令:实例化对象
  • invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。

我们根据字节码顺序来看:

// "1"+"2"+"3"被JVM转换成了字符串常量"123"存储到操作数栈
String s = "1" + "2" + "3";

跳过astore、dup等指令(不是本节重点)

/**
 * JVM将"1"存储到操作数栈
 * JVM用_new指令实例化一个StringBuilder对象,调用append()方法连接"1"
 * JVM将"2"存储到操作数栈
 * 调用append()方法连接"2"
 * 调用toString()转换成String类型
 * JVM_new指令再实例化一个StringBuilder对象,调用append()方法连接"12"
 * JVM将"3"存储到操作数栈
 * 调用append()方法连接"3"
 * 调用toString()转换成String类型
 * */
String s1 = "1";
String s2 = s1 + "2";
String s3 = s2 + "3";

当时用使用+操作符连接字符串时,为什么两者有无字符串对象就有区别呢?

是因为如果不出现字符串引用,字符串常量的值在编译期时就可以确定下来,所以不会使用到StringBuilder;如果出现字符串引用,JVM不能将字符串引用和字符串常量直接连接,所以将在运行期间动态生成StringBuilder对象,让它去实现连接。

说了StringBuilder,就不能不提StringBuffer,两者最大的区别是,前者线程不安全的,后者是线程安全的。不能一看是线程不安全就觉得不好,其实线程不安全比线程安全效率高,再者,因为我们写的一些代码不用线程安全这样多此一举。

特别注意!在循环中连接字符串时,最好不要出现字符串引用,因为每次循环都会新建StringBuilder,即使Java有垃圾回收机制,这样也很浪费资源。这时候就可以用StringBuilder来连接字符串。

public class Demo {
    public static void main(String[] args) {
        
        //我们这样写
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i < 10; i++) {
            builder.append(i);
        }
        System.out.println(builder.toString());

        //而不是这样写
        String s = "";
        for (int i = 1; i < 10; i++) {
            s = s + i;
        }
        System.out.println(s);

    }
}

总结

如果此博文谬误,还望各位路过的朋友指正!

参考网址


Original url: Access
Created at: 2019-12-26 18:01:11
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论