资讯专栏INFORMATION COLUMN

从字节码角度看String、StringBuffer、StringBuilder的不同

wua_wua2012 / 2177人阅读

摘要:官方说明将一个或多个类文件进行分解。显示静态常量为每个类中的方法打印反汇编代码例如字节码指令组成。在结果的行直接进行多次的拼接看看最后编译会是神马的这句话是对应声明了一个,然后每次拼接实际使用的是的方法。

Oracle官方说明:

javap
将一个或多个类文件进行分解。

使用简要说明
javap [options] classfile...

options
命令行选项,详细查看后面的Options介绍

classfile
一个或多个Class文件(多个使用空格分离),可以使用文件路径或者classPath下的文件或者输入URL

Description
javap命令分解卸一个或多个类文件。输出取决于所使用的选项。当没有使用任何选项,那么javap命令打印方案为protected和公共字段和方法。javap命令将输出打印到控制台。

Options
-help
--help
-?
打印帮助信息

-version
打印版本信息

-l
打印行内变量以及局部变量

-public
显示public访问修饰的内容

-protected
显示protected、public访问修饰的内容
-private
-p
显示所有的内容

-Joption
将指定的选项传递给JVM。例如:

javap -J-version
javap -J-Djava.security.manager -J-Djava.security.policy=MyPolicy MyClassName
For more information about JVM options, see the java command documentation.

-s
打印内部类签名

-sysinfo
显示系统信息(路径、大小、日期、MD5哈希)的类处理。
-constants
显示静态常量

-c
为每个类中的方法打印反汇编代码,例如,Java字节码指令组成,。


-verbose
打印堆栈大小,局部变量的数目和方法的参数。
Prints stack size, number of locals and arguments for methods.

-classpath path
指定javap命令使用的路径查找类。覆盖默认的或者当它被设置CLASSPATH环境变量。
Specifies the path the javap command uses to look up classes. Overrides the default or the CLASSPATH environment variable when it is set.

-bootclasspath path
指定的路径加载引导类。默认情况下,引导类类,实现核心Java平台位于jre / lib / rt。jar和其他几个jar文件。
Specifies the path from which to load bootstrap classes. By default, the bootstrap classes are the classes that implement the core Java platform located in jre/lib/rt.jar and several other JAR files.

-extdir dirs
增加一些扩展路径用以获取类库
Overrides the location at which installed extensions are searched for. The default location for extensions is the value of java.ext.dirs.
我们来看看String、StringBuffer、StringBuilder的不同 测试类
public class Test {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            //我们一般拼接字符的时候都不会拼接太多次100次其实就算比较多了
            contactStringWithLoop(100);
        }
        System.out.println(System.currentTimeMillis() - start);
        //916毫秒

        start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            //我们一般拼接字符的时候都不会拼接太多次100次其实就算比较多了
            contactStringWithStringBuilder(100);
        }

        System.out.println(System.currentTimeMillis() - start);
        //244毫秒

        start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            //我们一般拼接字符的时候都不会拼接太多次100次其实就算比较多了
            contactStringWithStringBuffer(100);
        }
        System.out.println(System.currentTimeMillis() - start);
        //620毫秒
    }

    /**
     * 直接拼接字符串
     *
     * @return
     */
    public static String contactString() {
        String string = "直接" + "对字符串" + "进行" + "多次的拼接"
                + "看看最后编译" + "的字节码" + "会是神马" + "样子" + "的";
        return string;
    }

    /**
     * 与上面的方法其实一致,只是在拼接中引入了一个多个变量
     *
     * @param str
     * @return
     */
    public static String contactStringWithParam(String str, String str2, String str3) {
        String string = "直接" + str3 + "进行" + "多次的拼接"
                + "看看最后编译" + str + "会是神马" + str2 + "的";
        return string;
    }

    /**
     * 通过循环来拼接字符串
     *
     * @param loopCount 循环的次数
     * @return
     */
    public static String contactStringWithLoop(int loopCount) {
        String string = "";
        for (int i = 0; i < loopCount; i++) {
            string += i;
        }
        return string;
    }

    /**
     * 使用StringBuffer循环拼接字符串
     *
     * @param loopCount
     * @return
     */
    public static String contactStringWithStringBuffer(int loopCount) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < loopCount; i++) {
            sb.append(i);
        }
        return sb.toString();
    }

    /**
     * 使用StringBuilder循环拼接字符串
     *
     * @param loopCount
     * @return
     */
    public static String contactStringWithStringBuilder(int loopCount) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < loopCount; i++) {
            sb.append(i);
        }
        return sb.toString();
    }
}  
使用javap -c Test 来获得字节码信息
Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1      
       4: iconst_0      
       5: istore_3      
       6: iload_3       
       7: ldc           #3                  // int 100000
       9: if_icmpge     24
      12: bipush        100
      14: invokestatic  #4                  // Method contactStringWithLoop:(I)Ljava/lang/String;
      17: pop           
      18: iinc          3, 1
      21: goto          6
      24: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      27: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      30: lload_1       
      31: lsub          
      32: invokevirtual #6                  // Method java/io/PrintStream.println:(J)V
      35: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      38: lstore_1      
      39: iconst_0      
      40: istore_3      
      41: iload_3       
      42: ldc           #3                  // int 100000
      44: if_icmpge     59
      47: bipush        100
      49: invokestatic  #7                  // Method contactStringWithStringBuilder:(I)Ljava/lang/String;
      52: pop           
      53: iinc          3, 1
      56: goto          41
      59: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      62: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      65: lload_1       
      66: lsub          
      67: invokevirtual #6                  // Method java/io/PrintStream.println:(J)V
      70: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      73: lstore_1      
      74: iconst_0      
      75: istore_3      
      76: iload_3       
      77: ldc           #3                  // int 100000
      79: if_icmpge     94
      82: bipush        100
      84: invokestatic  #8                  // Method contactStringWithStringBuffer:(I)Ljava/lang/String;
      87: pop           
      88: iinc          3, 1
      91: goto          76
      94: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      97: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
     100: lload_1       
     101: lsub          
     102: invokevirtual #6                  // Method java/io/PrintStream.println:(J)V
     105: return        

  public static java.lang.String contactString();
    Code:
       0: ldc           #9                  // String 直接对字符串进行多次的拼接看看最后编译的字节码会是神马样子的
       2: astore_0      
       3: aload_0       
       4: areturn       

  public static java.lang.String contactStringWithParam(java.lang.String, java.lang.String, java.lang.String);
    Code:
       0: new           #10                 // class java/lang/StringBuilder
       3: dup           
       4: invokespecial #11                 // Method java/lang/StringBuilder."":()V
       7: ldc           #12                 // String 直接
       9: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      12: aload_2       
      13: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      16: ldc           #14                 // String 进行
      18: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: ldc           #15                 // String 多次的拼接
      23: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #16                 // String 看看最后编译
      28: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: aload_0       
      32: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: ldc           #17                 // String 会是神马
      37: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      40: aload_1       
      41: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      44: ldc           #18                 // String 的
      46: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      52: astore_3      
      53: aload_3       
      54: areturn       

  public static java.lang.String contactStringWithLoop(int);
    Code:
       0: ldc           #20                 // String 
       2: astore_1      
       3: iconst_0      
       4: istore_2      
       5: iload_2       
       6: iload_0       
       7: if_icmpge     35
      10: new           #10                 // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #11                 // Method java/lang/StringBuilder."":()V
      17: aload_1       
      18: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: iload_2       
      22: invokevirtual #21                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      25: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      28: astore_1      
      29: iinc          2, 1
      32: goto          5
      35: aload_1       
      36: areturn       

  public static java.lang.String contactStringWithStringBuffer(int);
    Code:
       0: new           #22                 // class java/lang/StringBuffer
       3: dup           
       4: invokespecial #23                 // Method java/lang/StringBuffer."":()V
       7: astore_1      
       8: iconst_0      
       9: istore_2      
      10: iload_2       
      11: iload_0       
      12: if_icmpge     27
      15: aload_1       
      16: iload_2       
      17: invokevirtual #24                 // Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
      20: pop           
      21: iinc          2, 1
      24: goto          10
      27: aload_1       
      28: invokevirtual #25                 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
      31: areturn       

  public static java.lang.String contactStringWithStringBuilder(int);
    Code:
       0: new           #10                 // class java/lang/StringBuilder
       3: dup           
       4: invokespecial #11                 // Method java/lang/StringBuilder."":()V
       7: astore_1      
       8: iconst_0      
       9: istore_2      
      10: iload_2       
      11: iload_0       
      12: if_icmpge     27
      15: aload_1       
      16: iload_2       
      17: invokevirtual #21                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      20: pop           
      21: iinc          2, 1
      24: goto          10
      27: aload_1       
      28: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: areturn       
}
我们来分一下分解出的字节码:
contactString方法[在javac结果的64行]
  public static java.lang.String contactString();
    Code:
       0: ldc           #9                  // String 直接对字符串进行多次的拼接看看最后编译的字节码会是神马样子的
       2: astore_0      
       3: aload_0       
       4: areturn       

0: ldc #9 // String 直接对字符串进行多次的拼接看看最后编译的字节码会是神马样子的这里可以看出,编译器直接将编译结果进行了转换,没有使用+而直接使用了拼接后的字符串(因为不包含变量的拼接,所以可以预想到最终结果)
结论:对java中字符串直接拼接时可以直接使用+的方式来拼接。

contactStringWithParam[在javac结果的71行]
  public static java.lang.String contactStringWithParam(java.lang.String, java.lang.String, java.lang.String);
    Code:
       0: new           #10                 // class java/lang/StringBuilder
       3: dup           
       4: invokespecial #11                 // Method java/lang/StringBuilder."":()V
       7: ldc           #12                 // String 直接
       9: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      12: aload_2       
      13: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      16: ldc           #14                 // String 进行
      18: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: ldc           #15                 // String 多次的拼接
      23: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #16                 // String 看看最后编译
      28: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: aload_0       
      32: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: ldc           #17                 // String 会是神马
      37: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      40: aload_1       
      41: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      44: ldc           #18                 // String 的
      46: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      52: astore_3      
      53: aload_3       
      54: areturn       

0: new #10 // class java/lang/StringBuilder这句话是对应声明了一个StringBuilder,然后每次拼接实际使用的是StringBuilder的append方法。
结论:在针对这种没有循环但是有变变量拼接的字符串时,使用StringBuilder与使用String + 的方式没有区别,但是String + 的方式更加省时省力,而且相对清晰。

contactStringWithLoop[在javac结果的99行]
public static java.lang.String contactStringWithLoop(int);
    Code:
       0: ldc           #20                 // String 
       2: astore_1      
       3: iconst_0      
       4: istore_2      
       5: iload_2       
       6: iload_0       
       7: if_icmpge     35
      10: new           #10                 // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #11                 // Method java/lang/StringBuilder."":()V
      17: aload_1       
      18: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: iload_2       
      22: invokevirtual #21                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      25: invokevirtual #19                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      28: astore_1      
      29: iinc          2, 1
      32: goto          5
      35: aload_1       
      36: areturn       

首先先初始化循环变量:

         0: iconst_0      
         1: istore_1

这两行代码相当于 int i = 0 这句代码(iconst_0 是数字 0,istore_1 就是表示局部变量1,这里就是源码里的 i 了)

       5: iload_2
       6: iload_0
       7: if_icmpge     35

这三行意思就是 i 是否小于 10 ,小于则继续往下执行,否则就跳到 编号为 35的 return那里也即跳出for循环了,所以这里的实际意义是每次循环时新建一个StringBuilder,然后本次循环结束时返回StringBuilder的toString()的结果。

对比contactStringWithStringBuffer[在javac结果的122行]
  public static java.lang.String contactStringWithStringBuffer(int);
    Code:
       0: new           #22                 // class java/lang/StringBuffer
       3: dup           
       4: invokespecial #23                 // Method java/lang/StringBuffer."":()V
       7: astore_1      
       8: iconst_0      
       9: istore_2      
      10: iload_2       
      11: iload_0       
      12: if_icmpge     27
      15: aload_1       
      16: iload_2       
      17: invokevirtual #24                 // Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
      20: pop           
      21: iinc          2, 1
      24: goto          10
      27: aload_1       
      28: invokevirtual #25                 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
      31: areturn       

上面是先new StringBuilder,之后只是对这个StringBuilder进行append。
结论:当使用变量并循环拼接字符串时,应该使用StringBuilder的方式。

main方法中的测试结果也可以看出使用String、StringBuilder、StringBuffer分别进行10000次的百次字符拼接,StringBuilder的性能远远高于StringBuffer和String。【StringBuffer是线程安全的吗,所以损耗一些性能但是也优于String,主要是因为每次new StringBuilder的代价比较大】
我是广告

本人的直播课程在 7 月份就要开始了,希望小伙伴们支持一下,现在报名有优惠噢

https://segmentfault.com/l/15...

https://segmentfault.com/l/15...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67223.html

相关文章

  • 【Java系列】JVM角度深度解析Java核心类String不可变特性

    摘要:性能,大量运用在哈希的处理中,由于的不可变性,可以只计算一次哈希值,然后缓存在内部,后续直接取就好了。这是目前的一个底层字节码的实现,那么是不是没有使用或者的必要了呢。 凯伦说,公众号ID: KailunTalk,努力写出最优质的技术文章,欢迎关注探讨。 1. 前言 最近看到几个有趣的关于Java核心类String的问题。 String类是如何实现其不可变的特性的,设计成不可变的好处...

    afishhhhh 评论0 收藏0
  • 最最最常见Java面试题总结——第二周

    摘要:与都继承自类,在中也是使用字符数组保存字符串,,这两种对象都是可变的。采用字节码的好处语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。 String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的? String和StringBuffer、StringBuilder的区别 可变性...

    yearsj 评论0 收藏0
  • Java编程中那些再熟悉不过知识点(持续更新)

    摘要:语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。有针对不同系统的特定实现,,,目的是使用相同的字节码,它们都会给出相同的结果。项目主要基于捐赠的源代码。 本文来自于我的慕课网手记:Java编程中那些再熟悉不过的知识点,转载请保留链接 ;) 1. 面向对象和面向过程的区别 面向过程 优点: 性能比面向对象高。因为类调用时需要实例...

    taowen 评论0 收藏0
  • 超详细Java面试题总结(一)之Java基础知识篇

    摘要:最近在备战面试的过程中,整理一下面试题。成员变量如果没有被赋初值,则会自动以类型的默认值而赋值一种情况例外被修饰但没有被修饰的成员变量必须显示地赋值而局部变量则不会自动赋值。   最近在备战面试的过程中,整理一下面试题。大多数题目都是自己手敲的,网上也有很多这样的总结。自己感觉总是很乱,所以花了很久把自己觉得重要的东西总结了一下。 面向对象和面向过程的区别 面向过程:  优点:性能比面...

    vpants 评论0 收藏0
  • StringStringBuilder、StringBuffer 拼接测试

    摘要:测试拼接速度测试的结果在循环中,拼接字符串的速度远低于和利用查看字节码文件,寻找其中的差异命令行执行结果可以看出,拼接的时候也是通过的方法进行拼接的产生差异的原因是,在每次循环中,拼接的时候都了一个是线程安全的,只比稍慢了一点若不是 String、StringBuilder、StringBuffer 测试拼接速度 @Test public void testString() { ...

    20171112 评论0 收藏0

发表评论

0条评论

wua_wua2012

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<