隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0
          數據加載中……

          關于Java String對象創建問題解惑

          本文為原創,如需轉載,請注明作者和出處,謝謝!

          先看看下面的代碼

              
          public String makinStrings()
              {
                  String s 
          = "Fred";
                  s 
          = s + "47";
                  s 
          = s.substring(25);
                  s 
          = s.toUpperCase();
                  
          return s.toString();
              }


          問:調用makinStrings方法會創建幾個String對象呢。  答案:3個


              上面的方法有五條語句:現在讓我們來一條一條分析一下。

          String s = "Fred";   結論:創建了一個String對象

          這條語句相當于String s = new String("Fred");
          因此,毫無疑問,第一條語句創建了一個String對象,我想沒有有疑問吧?

          s = s + "47";   結論:未創建String對象

          這條語句也許很多人認為是創建了String對象,我一開始也是這么認為的。但是為了驗證我的想法。決定
          用點法術恢復這條語句的本來面目。(有很多時候,編譯器總是在里面搞一些小動作,javac.exe也不例外)

          現在找到這個程序所生成的.class文件(假設是Test.class),找一個反編譯工具,我推薦JAD,可以http://www.softpedia.com/progDownload/JAD-Download-85911.html下載
          下載后,有一個jad.exe,將其路徑放到環境變量path中(只限windows)。并在.class文件的當前路徑執行如下的命令:

          jad Test

          然后大喊一聲“還我本來面目”

          會在當前目錄下生成一個Test.jad文件,打開它,文件內容如下:

           
              
          public String makinStrings()
              {
                  String s 
          = "Fred";
                  s 
          = (new StringBuilder(String.valueOf(s))).append("47").toString();
                  s 
          = s.substring(25);
                  s 
          = s.toUpperCase();
                  
          return s.toString();
              }
           

              哈哈,其他的語句都沒變,只有第二條變長了,雖然多了個new,但是建立的是StringBuilder對象。原來
          這是java編譯器的優化處理。原則是能不建String對象就不建String對象。而是用StringBuilder對象
          加這些字符串連接起來,相當于一個字符串隊列。這種方式尤其被使用在循環中,大家可以看看下面的代碼:
                  String s = "";
                  for(int i=0; i < 10000000; i++)
                      s += "aa";
              沒有哪位老大認為這是建立了10000000個String對象吧。但不幸的是,上面的代碼雖然沒有建立10000000個String對象
          但卻建立了10000000個StringBuilder對象,那是為什么呢,自已用jad工具分析一下吧。
          正確的寫法應該是:

                  StringBuilder sb = new StringBuilder("");
                  for(int i=0; i < 10000000; i++)
                      sb.append(String.valueOf(i));

           s = s.substring(2, 5);     結論:創建了一個String對象
           也許有很多人一開始就認為這條語句是創建了一個String對象,那么恭喜你,這條語句確實創建了一個String對象
           實際上就是substring方法創建了一個String對象。這也沒什么復雜的,自已下一個JDK源代碼,看看substring是如何實現的
           就可以知道了。我先說一下吧。先不用管substring是如何實現的,反正在substring方法返回時使用了一個new顯式地建立了一個String對象
           不信自己看看源碼。
          s = s.toUpperCase();   結論:創建了一個String對象

          toUpperCase()和substring方法類似,在返回時也是使用了new建立了一個String對象。

          return s.toString();   結論:未創建String對象

          toString方法返回的就是this,因此,它的返回值就是s。

          這道題還算比較簡單,再給大家出一個更復雜一點的,也是關于String對象的創建的(只是改了一個原題)。

              public String makinStrings()
              {
                  String s 
          = "Fred";
                  s 
          = s + "Iloveyou.".substring(1).toLowerCase();
                  s 
          = s.substring(0);
                  s 
          = s.substring(0,1).toUpperCase();
                  
          return s.toString();
              }


          先公布答案吧,上述代碼也創建了3個String對象,哈哈!


          為什么呢?

          要想知道為什么,先得弄清楚substring、toLowerCase和toUpperCase什么時候創建String對象,什么時候不創建對象。

          substring方法在截取的子字符串長度等于原字符串時,直接返回原字符串。并不創建新的String對象。

          toLowerCase方法在字符串中更本沒有需要轉換的大寫字母時直接返回原字符串,如"abcd".toLowerCase()直接返回abcd,并不創建新的String對象

          toUpperCase方法和toLowerCase類似。"ABCD".toUpperCase()直接返回ABCD。


          知道了這個,上面的代碼就非常清楚了。

              public String makinStrings()
              {
                  String s 
          = "Fred";     // 創建一個String對象
                  s = s + "Iloveyou.".substring(1).toLowerCase();  // substring(1)創建一個String對象,由于toLowerCase()轉換的字符串是"loveyou.",沒有大寫字母,因此,它不創建新的String對象
                  s = s.substring(0);   // 由于substring(0)截獲的是s本身,因此,這條語句不創建新的String對象
                  s = s.substring(0,1).toUpperCase();  // substring(0,1)創建了一個String對象,但由于substring(0,1)的結果是"F",為一個大寫字母,因此,toUpperCase直接返回"F"本身。
                  return s.toString();
              }






          Android開發完全講義(第2版)(本書版權已輸出到臺灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2008-04-27 10:01 銀河使者 閱讀(3172) 評論(18)  編輯  收藏 所屬分類: java 原創

          評論

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          既然考慮編譯器因素了,為什么不考慮編譯器直接把這段代碼constant folding + constant propagation然后直接返回一個最終結果呢?
          2008-04-27 10:33 | ZelluX

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          好文!!!
          2008-04-27 10:33 | tomjamescn

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          如果之前有個函數,里面已經執行了過
          String s = "Fred";
          那么然后你再調用 makinStrings() 的時候,第一步應該不再創建一個 "Fred"了吧?
          2008-04-27 10:46 | stanleyxu

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          你是自己研究的吧,基本上差不多,只是
          String s = "Fred";
          這句不會在運行時創建String對象。
          s = (new StringBuilder(String.valueOf(s))).append("47").toString();
          這句StringBuilder.toString();是會創建String對象的。

          這樣一加一減,你的答案還是可用的。
          2008-04-27 11:01 | Matthew Chen

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          to Matthew Chen

          String s = "Fred"; 這句我查了一下bytecode,應該是一個壓棧的操作。如果要是創建String對象,就是四個String對象了。

          StringBuilder類的toString確實創建了一個String對象,下面是toString方法的代碼。

          public String toString() {
          // Create a copy, don't share the array
          return new String(value, 0, count);
          }

          2008-04-27 11:15 | 銀河使者

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          java應該和delphi和vc一樣,使用了引用計數原理。
          所以以此類推,給字符串賦值常量的時候,該常量是編譯時由編譯器(在堆上)創建。
          所以String s = "Fred";嚴格的說,第一次使用的時候是為它創建了一個實例。
          其他的部分你自己也已經說明了:
          如果小寫字符串執行toLowerCase()不會再(在棧上)創建實例,而是對它的引用計數加一。
          如果執行substring(0),同理。
          賦值的話,難說!如果有另外一個string b=s;然后你再執行s+='new..';可能就是會再創建一個實例,而不是不變了。(至少delphi里面是這樣的)

          麻煩lz驗證一下我的話,我只是從語義出發,做的一些推斷。
          2008-04-27 11:34 | stanleyxu

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          沒有必要太在意這個問題.
          因為不同的jdk下,比如sun jdk,ibm jdk等,答案是非唯一的.而且不同的jdk版本下,也有可能存在不同的答案.
          sun jdk是對String優化做的最多的.所以才這么拗.很多公司都拿string做面試題,沒有意義的.
          至于s=s+"47",至少在jdk5以下,在String pool中肯定會創建一個新的對象.但是之后的版本怎么處理,就沒去看過了.
          2008-04-27 12:04 | stone2083

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          補充一點:lz未說明從計算每步是否創建string實例還是問從函數返回時,內存中還有多少實例?這個結果是不一樣的。根據引用計數的原理,當一個字符串的refcount為0之后,會被釋放。而且根據編譯器的不同,可能會采取不同的策略。如果字符串變長,應該會去開辟一塊新的內存,而如果字符串變短,可能只是在原有的內存上做少許處理。
          2008-04-27 12:09 | stanleyxu

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          再補充一點:樓上的stone2083提醒了我,字符串變長的時候,應該要創建一個新的變量。我不太清楚string在java中如何分配的,一般應該是array形式,即申請一塊連續的內存。這樣的話,如果要加長字符串,應該要重新申請一塊新的連續內存了。因為編譯器無法保證當前字符串所占內存的后續連續地址是否是可用的。
          2008-04-27 12:12 | stanleyxu

          # re: 關于Java String對象創建問題解惑[未登錄]  回復  更多評論   

          to 銀河使者
          看不懂你的意思,是認同我的說法嗎?因為看你的回復和我的意思基本一致。

          to stanleyxu
          你提到了編譯器,我想那時生成的保存在靜態對象池中的string是字節碼的一部分,運行時調入內存的,而在內存中直接創建的string對象確實和普通的數組一樣的方式,沒有為連接做特別的結構考慮。
          2008-04-27 22:05 | Matthew Chen

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          據說這是一道SCJP的考試題,哈哈,哪天再弄點SCJP的試題分析一下。再來幾次頭腦風暴!
          2008-04-27 22:21 | 銀河使者

          # re: 關于Java String對象創建問題解惑[未登錄]  回復  更多評論   

          String s = "Fred"; 結論:創建了一個String對象

          這條語句相當于String s = new String("Fred");
          因此,毫無疑問,第一條語句創建了一個String對象,我想沒有有疑問吧?

          最近在聽浪曦網風中葉老師的面試視頻,聽他講 String s = new String("Fred");這句話應該是創建了2個對象,那你說String s = "Fred";這條語句相當于String s = new String("Fred");行嗎?到底誰是對的?
          2008-04-28 17:26 | 在威尼斯流浪

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          String s = new String("Fred")這句,new 肯定是創建了一個String對象。但是"Fred",我看了一下bytecode,好象是直接壓棧了,并不創建String對象。
          2008-04-28 19:29 | 銀河使者

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          下面是String s = new String("Fred")的bytecode,看看吧,只有一個new

          public class Test
          {
          public Test()
          {
          // 0 0:aload_0
          // 1 1:invokespecial #1 <Method void Object()>
          // 2 4:return
          }

          public void main(String args[])
          {
          String s = new String("Fred");
          // 0 0:new #2 <Class String>
          // 1 3:dup
          // 2 4:ldc1 #3 <String "Fred">
          // 3 6:invokespecial #4 <Method void String(String)>
          // 4 9:astore_2
          // 5 10:return
          }
          }

          還有就是在本文中我的分析有一點小毛病,就是String s = "Fred"的確在運行時不創建String對象,只是壓棧操作,另外說String s = "Fred"相當于String s = new String("Fred")也不嚴謹。從底層上它們還是有區別的。但可以肯定的是String s = new String("Fred")在當前方法中肯定是建立一個String對象,而String s = "Fred"并不創建對象。而StringBuilder的toString創建了一個String對象。這從上面的bytecode就可以看出。

          四樓的Matthew Chen說的沒錯。多謝提醒。
          2008-04-28 19:42 | 銀河使者

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          從評論看出lz也沒真正明白字符串分配究竟在干什么。

          String s1 = "Fred";
          String s2 = "Fred";
          // s1 == s2

          String s1 = new String("Fred");
          String s2 = "Fred";
          // s1 != s2

          String s1 = new String("Fred");
          String s2 = new String("Fred");
          // s1 != s2

          看看我之前的評論然后自己慢慢吧。
          2008-04-29 12:41 | stanleyxu

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          to tanleyxu

          從bytecode上看是String s = "abc" 不建立String對象。其實也沒必要這么較真。至于jvm內部到底如何工作,只有看jvm的源代碼了。jvm、MSIL這些東西對String對處理都不太一樣,無法只從語意上分析。不知道tanleyxu最后這個評論:

          String s1 = "Fred";
          String s2 = "Fred";
          // s1 == s2

          String s1 = new String("Fred");
          String s2 = "Fred";
          // s1 != s2

          String s1 = new String("Fred");
          String s2 = new String("Fred");
          // s1 != s2

          是什么意思.如果String s = "Fred", s是壓棧操作,用的是方法棧中的空間。new String(),用的是堆的空間,這沒錯。難道我最后補充的有問題。我可不這么認為。實際上String s = "aa"確實不創建新的String。反正在bytecode里沒找到new。



          2008-04-29 13:15 | 銀河使者

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          傻逼文章
          2008-06-06 10:04 | w

          # re: 關于Java String對象創建問題解惑  回復  更多評論   

          哈哈,還是得看評論啊。熱鬧。
          2015-05-28 17:30 | 莾s
          主站蜘蛛池模板: 南召县| 楚雄市| 庄浪县| 高邮市| 阿拉善左旗| 长海县| 祁阳县| 观塘区| 缙云县| 广宁县| 广灵县| 喀喇沁旗| 辉县市| 东明县| 缙云县| 方正县| 奈曼旗| 军事| 达州市| 桃江县| 盱眙县| 永平县| 蕉岭县| 北宁市| 小金县| 衡水市| 峨眉山市| 永嘉县| 广南县| 成安县| 镇平县| 叙永县| 彰化县| 盱眙县| 贵溪市| 鹿邑县| 巢湖市| 德兴市| 塔河县| 沭阳县| 肥乡县|