漫畫(huà):老板扣了我1000,因?yàn)槲覜](méi)記住阿里巴巴開(kāi)發(fā)手冊(cè)的這條規(guī)則。

本文故事構(gòu)思來(lái)源于脈脈上的一篇帖子“一行代碼引發(fā)的血案”。

image

其實(shí)關(guān)于字符串的文章,我之前也寫(xiě)過(guò)一篇《詭異的字符串問(wèn)題》,字符串對(duì)于我們開(kāi)發(fā)者而言,可以用最近很流行的一句話“用起來(lái)好嗨喲,仿佛人生達(dá)到了巔峰”。

確實(shí)大家都用的很嗨,很便利,但 JDK 的工程師在背后付出了努力又有幾個(gè)人真的在意呢?

咱們今天就通過(guò)一個(gè)例子來(lái)詳細(xì)的說(shuō)明。

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

        // 無(wú)變量的字符串拼接
      String s = "aa"+"bb"+"dd";
        System.out.println(s);
        // 有變量的字符串拼接
        String g = "11"+s+5;
        System.out.println(g);
        // 循環(huán)中使用字符串拼接
        String a = "0";
        for (int i = 1; i < 10; i++) {
            a = a + i;
        }
        System.out.println(a);
        // 循環(huán)外定義StringBuilder
        StringBuilder b = new StringBuilder();
        for (int i = 1; i < 10; i++) {
            b.append(i);
        }
        System.out.println(b);
    }
}

有同學(xué)可能會(huì)說(shuō),這么一段代碼,怎么來(lái)區(qū)分呢?既然我在對(duì)話中說(shuō)了 Java 從 JDK5 開(kāi)始,便在編譯期間進(jìn)行了優(yōu)化,那么編譯期間 javac 命令主要干了什么事情呢?一句話歸根結(jié)底,那么肯定就是把 .java 源碼編譯成 .class 文件,也就是我們常說(shuō)的中間語(yǔ)言——字節(jié)碼。然后 JVM 引擎再對(duì) .class 文件進(jìn)行驗(yàn)證,解析,翻譯成本地可執(zhí)行的機(jī)器指令,這只是一個(gè)最簡(jiǎn)單的模型,其實(shí)現(xiàn)在的 JVM 引擎后期還會(huì)做很多優(yōu)化,比如代碼熱點(diǎn)分析,JIT編譯,逃逸分析等。

說(shuō)到這里,我記得之前群里有同學(xué)說(shuō),字節(jié)碼長(zhǎng)得太丑了,看了第一眼就不想看第二眼,哈哈,丑是丑點(diǎn),但是很有內(nèi)涵,能量強(qiáng)大,實(shí)現(xiàn)了跨平臺(tái)性。

關(guān)于怎么查看字節(jié)碼,我之前分享過(guò)兩個(gè)工具,一個(gè) JDK 自帶的 javap,另一個(gè)IDEA的插件 jclasslib Bytecode viewer。今天給你再分享一個(gè),我之前破解 apk 常用的工具 jad,它會(huì)讓你看字節(jié)碼文件輕松很多。

先說(shuō)一下,我分別用 Jdk 1.6 - 1.8 自帶的 javap 工具進(jìn)行了反編譯,發(fā)現(xiàn)生成的 JVM 指令是一樣的,所以在此處不會(huì)列出每一個(gè)版本生成的指令文件。為了便于大家閱讀指令文件,這里用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 StringTest()
    {
    //    0    0:aload_0         
    //    1    1:invokespecial   #1   <Method void Object()>
    //    2    4:return          
    }

    public static void main(String args[])
    {
        String s = "aabbdd";
    //    0    0:ldc1            #2   <String "aabbdd">
    //    1    2:astore_1        
        System.out.println(s);
    //    2    3:getstatic       #3   <Field PrintStream System.out>
    //    3    6:aload_1         
    //    4    7:invokevirtual   #4   <Method void PrintStream.println(String)>
        String g = (new StringBuilder()).append("11").append(s).append(5).toString();
    //    5   10:new             #5   <Class StringBuilder>
    //    6   13:dup             
    //    7   14:invokespecial   #6   <Method void StringBuilder()>
    //    8   17:ldc1            #7   <String "11">
    //    9   19:invokevirtual   #8   <Method StringBuilder StringBuilder.append(String)>
    //   10   22:aload_1         
    //   11   23:invokevirtual   #8   <Method StringBuilder StringBuilder.append(String)>
    //   12   26:iconst_5        
    //   13   27:invokevirtual   #9   <Method StringBuilder StringBuilder.append(int)>
    //   14   30:invokevirtual   #10  <Method String StringBuilder.toString()>
    //   15   33:astore_2        
        System.out.println(g);
    //   16   34:getstatic       #3   <Field PrintStream System.out>
    //   17   37:aload_2         
    //   18   38:invokevirtual   #4   <Method void PrintStream.println(String)>
        String a = "0";
    //   19   41:ldc1            #11  <String "0">
    //   20   43:astore_3        
        for(int i = 1; i < 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             #5   <Class StringBuilder>
    //   27   57:dup             
    //   28   58:invokespecial   #6   <Method void StringBuilder()>
    //   29   61:aload_3         
    //   30   62:invokevirtual   #8   <Method StringBuilder StringBuilder.append(String)>
    //   31   65:iload           4
    //   32   67:invokevirtual   #9   <Method StringBuilder StringBuilder.append(int)>
    //   33   70:invokevirtual   #10  <Method String StringBuilder.toString()>
    //   34   73:astore_3        

    //   35   74:iinc            4  1
    //*  36   77:goto            47
        System.out.println(a);
    //   37   80:getstatic       #3   <Field PrintStream System.out>
    //   38   83:aload_3         
    //   39   84:invokevirtual   #4   <Method void PrintStream.println(String)>
        StringBuilder b = new StringBuilder();
    //   40   87:new             #5   <Class StringBuilder>
    //   41   90:dup             
    //   42   91:invokespecial   #6   <Method void StringBuilder()>
    //   43   94:astore          4
        for(int i = 1; i < 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   #9   <Method StringBuilder StringBuilder.append(int)>
    //   52  113:pop             

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

這里說(shuō)一下分析結(jié)果。

1、無(wú)變量的字符串拼接,在編譯期間值都確定了,所以 javac 工具幫我們把它直接編譯成一個(gè)字符常量。

2、有變量的字符串拼接,在編譯期間變量的值無(wú)法確定,所以運(yùn)行期間會(huì)生成一個(gè)StringBuilder 對(duì)象。

3、循環(huán)中使用字符串拼接,循環(huán)內(nèi),每循環(huán)一次就會(huì)產(chǎn)生一個(gè)新的 StringBuilder 對(duì)象,對(duì)資源有一定的損耗。

4、循環(huán)外使用 StringBuilder,循環(huán)內(nèi)再執(zhí)行 append() 方法拼接字符串,只會(huì)成一個(gè) StringBuilder 對(duì)象。

因此,對(duì)于有循環(huán)的字符串拼接操作,建議使用 StringBuilder 和 StringBuffer,對(duì)性能會(huì)有一定的提升。

其實(shí)上面的結(jié)論,《阿里巴巴Java開(kāi)發(fā)手冊(cè)》中有所提到,此文正好與該條結(jié)論相對(duì)應(yīng)。

image

一個(gè)簡(jiǎn)單的字符串,用起來(lái)確實(shí)簡(jiǎn)單,背后付出了多少工程師的心血,在此,深深地佩服詹爺。

image

博客已遷移,歡迎關(guān)注 最新博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,804評(píng)論 3 93
  • 三重:代碼、底層內(nèi)存、源碼 第一階段:開(kāi)發(fā)常用JavaSE基礎(chǔ)、IDE、Maven、Gradle、SVN、Git、...
    guodd369閱讀 17,521評(píng)論 1 44
  • 那年,16歲。 夏天的海像藍(lán)天跌入水面的藍(lán)。 漁號(hào)子響了起來(lái),紅魚(yú)紋頭的木船開(kāi)動(dòng)著。 漁民的船像犁田的牛仔海面犁開(kāi)...
    七星二少閱讀 302評(píng)論 0 0
  • 春天的花開(kāi)秋天的風(fēng)以及冬天的落陽(yáng) 憂郁的青春年少的我曾經(jīng)無(wú)知的這么想 風(fēng)車在四季輪回的歌里它天天的流轉(zhuǎn) 風(fēng) 花雪月...
    歐陽(yáng)小刀閱讀 311評(píng)論 0 1
  • (01) 磊是單位的中層領(lǐng)導(dǎo),精干、上進(jìn)、有責(zé)任心。 但,他最近很郁悶、苦惱。 原因是精干、上進(jìn)的他所帶的團(tuán)隊(duì)表現(xiàn)...
    如云生長(zhǎng)閱讀 599評(píng)論 0 2

友情鏈接更多精彩內(nèi)容