隨筆 - 312, 文章 - 14, 評(píng)論 - 1393, 引用 - 0
          數(shù)據(jù)加載中……

          關(guān)于Java String對(duì)象創(chuàng)建問題解惑

          本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!

          先看看下面的代碼

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


          問:調(diào)用makinStrings方法會(huì)創(chuàng)建幾個(gè)String對(duì)象呢。  答案:3個(gè)


              上面的方法有五條語(yǔ)句:現(xiàn)在讓我們來(lái)一條一條分析一下。

          String s = "Fred";   結(jié)論:創(chuàng)建了一個(gè)String對(duì)象

          這條語(yǔ)句相當(dāng)于String s = new String("Fred");
          因此,毫無(wú)疑問,第一條語(yǔ)句創(chuàng)建了一個(gè)String對(duì)象,我想沒有有疑問吧?

          s = s + "47";   結(jié)論:未創(chuàng)建String對(duì)象

          這條語(yǔ)句也許很多人認(rèn)為是創(chuàng)建了String對(duì)象,我一開始也是這么認(rèn)為的。但是為了驗(yàn)證我的想法。決定
          用點(diǎn)法術(shù)恢復(fù)這條語(yǔ)句的本來(lái)面目。(有很多時(shí)候,編譯器總是在里面搞一些小動(dòng)作,javac.exe也不例外)

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

          jad Test

          然后大喊一聲“還我本來(lái)面目”

          會(huì)在當(dāng)前目錄下生成一個(gè)Test.jad文件,打開它,文件內(nèi)容如下:

           
              
          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();
              }
           

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

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

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

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

          return s.toString();   結(jié)論:未創(chuàng)建String對(duì)象

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

          這道題還算比較簡(jiǎn)單,再給大家出一個(gè)更復(fù)雜一點(diǎn)的,也是關(guān)于String對(duì)象的創(chuàng)建的(只是改了一個(gè)原題)。

              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();
              }


          先公布答案吧,上述代碼也創(chuàng)建了3個(gè)String對(duì)象,哈哈!


          為什么呢?

          要想知道為什么,先得弄清楚substring、toLowerCase和toUpperCase什么時(shí)候創(chuàng)建String對(duì)象,什么時(shí)候不創(chuàng)建對(duì)象。

          substring方法在截取的子字符串長(zhǎng)度等于原字符串時(shí),直接返回原字符串。并不創(chuàng)建新的String對(duì)象。

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

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


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

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






          Android開發(fā)完全講義(第2版)(本書版權(quán)已輸出到臺(tái)灣)

          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 銀河使者 閱讀(3173) 評(píng)論(18)  編輯  收藏 所屬分類: java 原創(chuàng)

          評(píng)論

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          如果之前有個(gè)函數(shù),里面已經(jīng)執(zhí)行了過(guò)
          String s = "Fred";
          那么然后你再調(diào)用 makinStrings() 的時(shí)候,第一步應(yīng)該不再創(chuàng)建一個(gè) "Fred"了吧?
          2008-04-27 10:46 | stanleyxu

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          你是自己研究的吧,基本上差不多,只是
          String s = "Fred";
          這句不會(huì)在運(yùn)行時(shí)創(chuàng)建String對(duì)象。
          s = (new StringBuilder(String.valueOf(s))).append("47").toString();
          這句StringBuilder.toString();是會(huì)創(chuàng)建String對(duì)象的。

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          to Matthew Chen

          String s = "Fred"; 這句我查了一下bytecode,應(yīng)該是一個(gè)壓棧的操作。如果要是創(chuàng)建String對(duì)象,就是四個(gè)String對(duì)象了。

          StringBuilder類的toString確實(shí)創(chuàng)建了一個(gè)String對(duì)象,下面是toString方法的代碼。

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

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          補(bǔ)充一點(diǎn):lz未說(shuō)明從計(jì)算每步是否創(chuàng)建string實(shí)例還是問從函數(shù)返回時(shí),內(nèi)存中還有多少實(shí)例?這個(gè)結(jié)果是不一樣的。根據(jù)引用計(jì)數(shù)的原理,當(dāng)一個(gè)字符串的refcount為0之后,會(huì)被釋放。而且根據(jù)編譯器的不同,可能會(huì)采取不同的策略。如果字符串變長(zhǎng),應(yīng)該會(huì)去開辟一塊新的內(nèi)存,而如果字符串變短,可能只是在原有的內(nèi)存上做少許處理。
          2008-04-27 12:09 | stanleyxu

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          再補(bǔ)充一點(diǎn):樓上的stone2083提醒了我,字符串變長(zhǎng)的時(shí)候,應(yīng)該要?jiǎng)?chuàng)建一個(gè)新的變量。我不太清楚string在java中如何分配的,一般應(yīng)該是array形式,即申請(qǐng)一塊連續(xù)的內(nèi)存。這樣的話,如果要加長(zhǎng)字符串,應(yīng)該要重新申請(qǐng)一塊新的連續(xù)內(nèi)存了。因?yàn)榫幾g器無(wú)法保證當(dāng)前字符串所占內(nèi)存的后續(xù)連續(xù)地址是否是可用的。
          2008-04-27 12:12 | stanleyxu

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑[未登錄]  回復(fù)  更多評(píng)論   

          to 銀河使者
          看不懂你的意思,是認(rèn)同我的說(shuō)法嗎?因?yàn)榭茨愕幕貜?fù)和我的意思基本一致。

          to stanleyxu
          你提到了編譯器,我想那時(shí)生成的保存在靜態(tài)對(duì)象池中的string是字節(jié)碼的一部分,運(yùn)行時(shí)調(diào)入內(nèi)存的,而在內(nèi)存中直接創(chuàng)建的string對(duì)象確實(shí)和普通的數(shù)組一樣的方式,沒有為連接做特別的結(jié)構(gòu)考慮。
          2008-04-27 22:05 | Matthew Chen

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          據(jù)說(shuō)這是一道SCJP的考試題,哈哈,哪天再弄點(diǎn)SCJP的試題分析一下。再來(lái)幾次頭腦風(fēng)暴!
          2008-04-27 22:21 | 銀河使者

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑[未登錄]  回復(fù)  更多評(píng)論   

          String s = "Fred"; 結(jié)論:創(chuàng)建了一個(gè)String對(duì)象

          這條語(yǔ)句相當(dāng)于String s = new String("Fred");
          因此,毫無(wú)疑問,第一條語(yǔ)句創(chuàng)建了一個(gè)String對(duì)象,我想沒有有疑問吧?

          最近在聽浪曦網(wǎng)風(fēng)中葉老師的面試視頻,聽他講 String s = new String("Fred");這句話應(yīng)該是創(chuàng)建了2個(gè)對(duì)象,那你說(shuō)String s = "Fred";這條語(yǔ)句相當(dāng)于String s = new String("Fred");行嗎?到底誰(shuí)是對(duì)的?
          2008-04-28 17:26 | 在威尼斯流浪

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          下面是String s = new String("Fred")的bytecode,看看吧,只有一個(gè)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
          }
          }

          還有就是在本文中我的分析有一點(diǎn)小毛病,就是String s = "Fred"的確在運(yùn)行時(shí)不創(chuàng)建String對(duì)象,只是壓棧操作,另外說(shuō)String s = "Fred"相當(dāng)于String s = new String("Fred")也不嚴(yán)謹(jǐn)。從底層上它們還是有區(qū)別的。但可以肯定的是String s = new String("Fred")在當(dāng)前方法中肯定是建立一個(gè)String對(duì)象,而String s = "Fred"并不創(chuàng)建對(duì)象。而StringBuilder的toString創(chuàng)建了一個(gè)String對(duì)象。這從上面的bytecode就可以看出。

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          從評(píng)論看出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

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          to tanleyxu

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

          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(),用的是堆的空間,這沒錯(cuò)。難道我最后補(bǔ)充的有問題。我可不這么認(rèn)為。實(shí)際上String s = "aa"確實(shí)不創(chuàng)建新的String。反正在bytecode里沒找到new。



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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

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

          # re: 關(guān)于Java String對(duì)象創(chuàng)建問題解惑  回復(fù)  更多評(píng)論   

          哈哈,還是得看評(píng)論啊。熱鬧。
          2015-05-28 17:30 | 莾s
          主站蜘蛛池模板: 安庆市| 松原市| 云南省| 蚌埠市| 邢台市| 龙州县| 筠连县| 阿瓦提县| 上栗县| 那坡县| 丰原市| 兰坪| 双牌县| 崇左市| 安陆市| 阳东县| 丹寨县| 钟祥市| 乡城县| 资源县| 巩义市| 常熟市| 巴彦淖尔市| 青阳县| 巴林右旗| 托里县| 丰都县| 民权县| 昌都县| 新巴尔虎左旗| 海林市| 旺苍县| 台北市| 正定县| 安福县| 会同县| 嘉鱼县| 水富县| 施甸县| 丰都县| 通海县|