本文故事构思来源于脉脉上的一篇帖子“一行代码引发的血案”。

其实关于字符串的文章,我之前也写过一篇《诡异的字符串问题》,字符串对于我们开发者而言,可以用最近很流行的一句话“用起来好嗨哟,仿佛人生达到了巅峰”。

确实大家都用的很嗨,很便利,但 JDK 的工程师在背后付出了努力又有几个人真的在意呢?

咱们今天就通过一个例子来详细的说明。

public class StringTest {
    public static void main(String[] args) {
    // 无变量的字符串拼接
  String s = <span class="hljs-string">"aa"</span>+<span class="hljs-string">"bb"</span>+<span class="hljs-string">"dd"</span>;
    System.out.println(s);
    // 有变量的字符串拼接
    String g = <span class="hljs-string">"11"</span>+s+5;
    System.out.println(g);
    // 循环中使用字符串拼接
    String a = <span class="hljs-string">"0"</span>;
    <span class="hljs-keyword">for</span> (int i = 1; i &lt; 10; i++) {
        a = a + i;
    }
    System.out.println(a);
    // 循环外定义StringBuilder
    StringBuilder b = new StringBuilder();
    <span class="hljs-keyword">for</span> (int i = 1; i &lt; 10; i++) {
        b.append(i);
    }
    System.out.println(b);
}

}

复制代码

有同学可能会说,这么一段代码,怎么来区分呢?既然我在对话中说了 Java 从 JDK5 开始,便在编译期间进行了优化,那么编译期间 javac 命令主要干了什么事情呢?一句话归根结底,那么肯定就是把 .java 源码编译成 .class 文件,也就是我们常说的中间语言——字节码。然后 JVM 引擎再对 .class 文件进行验证,解析,翻译成本地可执行的机器指令,这只是一个最简单的模型,其实现在的 JVM 引擎后期还会做很多优化,比如代码热点分析,JIT 编译,逃逸分析等。

说到这里,我记得之前群里有同学说,字节码长得太丑了,看了第一眼就不想看第二眼,哈哈,丑是丑点,但是很有内涵,能量强大,实现了跨平台性。

关于怎么查看字节码,我之前分享过两个工具,一个 JDK 自带的 javap,另一个 IDEA 的插件 jclasslib Bytecode viewer。今天给你再分享一个,我之前破解 apk 常用的工具 jad,它会让你看字节码文件轻松很多。

先说一下,我分别用 Jdk 1.6 - 1.8 自带的 javap 工具进行了反编译,发现生成的 JVM 指令是一样的,所以在此处不会列出每一个版本生成的指令文件。为了便于大家阅读指令文件,这里用 jad 工具生成,代码如下。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) annotate 
// Source File Name:   StringTest.java

import java.io.PrintStream;

public class StringTest
{

public <span class="hljs-function"><span class="hljs-title">StringTest</span></span>()
{
//    0    0:aload_0         
//    1    1:invokespecial   <span class="hljs-comment">#1   &lt;Method void Object()&gt;</span>
//    2    4:<span class="hljs-built_in">return</span>          
}

public static void main(String args[])
{
    String s = <span class="hljs-string">"aabbdd"</span>;
//    0    0:ldc1            <span class="hljs-comment">#2   &lt;String "aabbdd"&gt;</span>
//    1    2:astore_1        
    System.out.println(s);
//    2    3:getstatic       <span class="hljs-comment">#3   &lt;Field PrintStream System.out&gt;</span>
//    3    6:aload_1         
//    4    7:invokevirtual   <span class="hljs-comment">#4   &lt;Method void PrintStream.println(String)&gt;</span>
    String g = (new StringBuilder()).append(<span class="hljs-string">"11"</span>).append(s).append(5).toString();
//    5   10:new             <span class="hljs-comment">#5   &lt;Class StringBuilder&gt;</span>
//    6   13:dup             
//    7   14:invokespecial   <span class="hljs-comment">#6   &lt;Method void StringBuilder()&gt;</span>
//    8   17:ldc1            <span class="hljs-comment">#7   &lt;String "11"&gt;</span>
//    9   19:invokevirtual   <span class="hljs-comment">#8   &lt;Method StringBuilder StringBuilder.append(String)&gt;</span>
//   10   22:aload_1         
//   11   23:invokevirtual   <span class="hljs-comment">#8   &lt;Method StringBuilder StringBuilder.append(String)&gt;</span>
//   12   26:iconst_5        
//   13   27:invokevirtual   <span class="hljs-comment">#9   &lt;Method StringBuilder StringBuilder.append(int)&gt;</span>
//   14   30:invokevirtual   <span class="hljs-comment">#10  &lt;Method String StringBuilder.toString()&gt;</span>
//   15   33:astore_2        
    System.out.println(g);
//   16   34:getstatic       <span class="hljs-comment">#3   &lt;Field PrintStream System.out&gt;</span>
//   17   37:aload_2         
//   18   38:invokevirtual   <span class="hljs-comment">#4   &lt;Method void PrintStream.println(String)&gt;</span>
    String a = <span class="hljs-string">"0"</span>;
//   19   41:ldc1            <span class="hljs-comment">#11  &lt;String "0"&gt;</span>
//   20   43:astore_3        
    <span class="hljs-keyword">for</span>(int i = 1; i &lt; 10; i++)
//*  21   44:iconst_1        
//*  22   45:istore          4
//*  23   47:iload           4
//*  24   49:bipush          10
//*  25   51:icmpge          80
        a = (new StringBuilder()).append(a).append(i).toString();
//   26   54:new             <span class="hljs-comment">#5   &lt;Class StringBuilder&gt;</span>
//   27   57:dup             
//   28   58:invokespecial   <span class="hljs-comment">#6   &lt;Method void StringBuilder()&gt;</span>
//   29   61:aload_3         
//   30   62:invokevirtual   <span class="hljs-comment">#8   &lt;Method StringBuilder StringBuilder.append(String)&gt;</span>
//   31   65:iload           4
//   32   67:invokevirtual   <span class="hljs-comment">#9   &lt;Method StringBuilder StringBuilder.append(int)&gt;</span>
//   33   70:invokevirtual   <span class="hljs-comment">#10  &lt;Method String StringBuilder.toString()&gt;</span>
//   34   73:astore_3        

//   35   74:iinc            4  1
//*  36   77:goto            47
    System.out.println(a);
//   37   80:getstatic       <span class="hljs-comment">#3   &lt;Field PrintStream System.out&gt;</span>
//   38   83:aload_3         
//   39   84:invokevirtual   <span class="hljs-comment">#4   &lt;Method void PrintStream.println(String)&gt;</span>
    StringBuilder b = new StringBuilder();
//   40   87:new             <span class="hljs-comment">#5   &lt;Class StringBuilder&gt;</span>
//   41   90:dup             
//   42   91:invokespecial   <span class="hljs-comment">#6   &lt;Method void StringBuilder()&gt;</span>
//   43   94:astore          4
    <span class="hljs-keyword">for</span>(int i = 1; i &lt; 10; i++)
//*  44   96:iconst_1        
//*  45   97:istore          5
//*  46   99:iload           5
//*  47  101:bipush          10
//*  48  103:icmpge          120
        b.append(i);
//   49  106:aload           4
//   50  108:iload           5
//   51  110:invokevirtual   <span class="hljs-comment">#9   &lt;Method StringBuilder StringBuilder.append(int)&gt;</span>
//   52  113:pop             

//   53  114:iinc            5  1
//*  54  117:goto            99
    System.out.println(b);
//   55  120:getstatic       <span class="hljs-comment">#3   &lt;Field PrintStream System.out&gt;</span>
//   56  123:aload           4
//   57  125:invokevirtual   <span class="hljs-comment">#12  &lt;Method void PrintStream.println(Object)&gt;</span>
//   58  128:<span class="hljs-built_in">return</span>          
}

}

复制代码

这里说一下分析结果。

1、无变量的字符串拼接,在编译期间值都确定了,所以 javac 工具帮我们把它直接编译成一个字符常量。

2、有变量的字符串拼接,在编译期间变量的值无法确定,所以运行期间会生成一个 StringBuilder 对象。

3、循环中使用字符串拼接,循环内,每循环一次就会产生一个新的 StringBuilder 对象,对资源有一定的损耗。

4、循环外使用 StringBuilder,循环内再执行 append() 方法拼接字符串,只会成一个 StringBuilder 对象。

因此,对于有循环的字符串拼接操作,建议使用 StringBuilder 和 StringBuffer,对性能会有一定的提升。

其实上面的结论,《阿里巴巴 Java 开发手册》中有所提到,此文正好与该条结论相对应。

一个简单的字符串,用起来确实简单,背后付出了多少工程师的心血,在此,深深地佩服詹爷。

PS:本文原创发布于微信公众号 「Java 面试那些事儿」,关注并回复「1024」,免费领学习资料。 原文地址


  • Java

    Java,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台的总称。用 Java 实现的 HotJava 浏览器(支持 Java applet)显示了 Java 的魅力:跨平台、动态的…

    380 引用 • 6 回帖
感谢    赞同    分享    收藏    关注    反对    举报    ...