漫畫:老闆扣了我1000,因為我沒記住阿里巴巴開發手冊的這條規則。
閱讀 18
漫畫:老闆扣了我1000,因為我沒記住阿里巴巴開發手冊的這條規則。
本文故事構思來源於脈脈上的一篇帖子“一行程式碼引發的血案”。
其實關於字串的文章,我之前也寫過一篇《詭異的字串問題》,字串對於我們開發者而言,可以用最近很流行的一句話“用起來好嗨喲,彷彿人生達到了巔峰”。
確實大家都用的很嗨,很便利,但 JDK 的工程師在背後付出了努力又有幾個人真的在意呢?
咱們今天就通過一個例子來詳細的說明。
public class StringTest { public static void main(String[] args) { // 無變數的字串拼接 String s = "aa"+"bb"+"dd"; System.out.println(s); // 有變數的字串拼接 String g = "11"+s+5; System.out.println(g); // 迴圈中使用字串拼接 String a = "0"; for (int i = 1; i < 10; i++) { a = a + i; } System.out.println(a); // 迴圈外定義StringBuilder StringBuilder b = new StringBuilder(); for (int i = 1; i < 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 StringTest() { //00:aload_0 //11:invokespecial#1<Method void Object()> //24:return } public static void main(String args[]) { String s = "aabbdd"; //00:ldc1#2<String "aabbdd"> //12:astore_1 System.out.println(s); //23:getstatic#3<Field PrintStream System.out> //36:aload_1 //47:invokevirtual#4<Method void PrintStream.println(String)> String g = (new StringBuilder()).append("11").append(s).append(5).toString(); //510:new#5<Class StringBuilder> //613:dup //714:invokespecial#6<Method void StringBuilder()> //817:ldc1#7<String "11"> //919:invokevirtual#8<Method StringBuilder StringBuilder.append(String)> //1022:aload_1 //1123:invokevirtual#8<Method StringBuilder StringBuilder.append(String)> //1226:iconst_5 //1327:invokevirtual#9<Method StringBuilder StringBuilder.append(int)> //1430:invokevirtual#10<Method String StringBuilder.toString()> //1533:astore_2 System.out.println(g); //1634:getstatic#3<Field PrintStream System.out> //1737:aload_2 //1838:invokevirtual#4<Method void PrintStream.println(String)> String a = "0"; //1941:ldc1#11<String "0"> //2043:astore_3 for(int i = 1; i < 10; i++) //*2144:iconst_1 //*2245:istore4 //*2347:iload4 //*2449:bipush10 //*2551:icmpge80 a = (new StringBuilder()).append(a).append(i).toString(); //2654:new#5<Class StringBuilder> //2757:dup //2858:invokespecial#6<Method void StringBuilder()> //2961:aload_3 //3062:invokevirtual#8<Method StringBuilder StringBuilder.append(String)> //3165:iload4 //3267:invokevirtual#9<Method StringBuilder StringBuilder.append(int)> //3370:invokevirtual#10<Method String StringBuilder.toString()> //3473:astore_3 //3574:iinc41 //*3677:goto47 System.out.println(a); //3780:getstatic#3<Field PrintStream System.out> //3883:aload_3 //3984:invokevirtual#4<Method void PrintStream.println(String)> StringBuilder b = new StringBuilder(); //4087:new#5<Class StringBuilder> //4190:dup //4291:invokespecial#6<Method void StringBuilder()> //4394:astore4 for(int i = 1; i < 10; i++) //*4496:iconst_1 //*4597:istore5 //*4699:iload5 //*47101:bipush10 //*48103:icmpge120 b.append(i); //49106:aload4 //50108:iload5 //51110:invokevirtual#9<Method StringBuilder StringBuilder.append(int)> //52113:pop //53114:iinc51 //*54117:goto99 System.out.println(b); //55120:getstatic#3<Field PrintStream System.out> //56123:aload4 //57125:invokevirtual#12<Method void PrintStream.println(Object)> //58128:return } } 複製程式碼
這裡說一下分析結果。
1、無變數的字串拼接,在編譯期間值都確定了,所以 javac 工具幫我們把它直接編譯成一個字元常量。
2、有變數的字串拼接,在編譯期間變數的值無法確定,所以執行期間會生成一個StringBuilder 物件。
3、迴圈中使用字串拼接,迴圈內,每迴圈一次就會產生一個新的 StringBuilder 物件,對資源有一定的損耗。
4、迴圈外使用 StringBuilder,迴圈內再執行 append() 方法拼接字串,只會成一個 StringBuilder 物件。
因此,對於有迴圈的字串拼接操作,建議使用 StringBuilder 和 StringBuffer,對效能會有一定的提升。
其實上面的結論,《阿里巴巴Java開發手冊》中有所提到,此文正好與該條結論相對應。
一個簡單的字串,用起來確實簡單,背後付出了多少工程師的心血,在此,深深地佩服詹爺。
PS:本文原創釋出於微信公眾號 「Java面試那些事兒」,關注並回復「1024」,免費領學習資料。原文地址