posts - 241,  comments - 116,  trackbacks - 0

                   JVM一樣,.NETCLR也提供了add等專用指令完成加減乘除功能。

                   從開發者使用角度而言,C#int既具有與Java的原始數據類型int一樣的在虛擬機級別的專用指令,又具有Java包裝類Integer所擁有的一些功能,還同時避免了JavaInteger的那種比較古怪的特性,個人認為,C#中的intJava中的int/Integer更好用,更易用。

                   但從探索技術內幕而言則大不一樣,Java使用Integer一個類就“搞定”了所有常用的整數處理功能,而對于.NETSystem.In32結構,好奇的朋友不妨用Reflector去查看一下相關的源碼,會發現System.Int32在內部許多地方使用了Number類所封裝的功能,還用到了NumberFormatInfo(提供數字的格式化信息)、CultureInfo(提供當前文化信息)等相關類型,如果再算加上一堆的接口,那真是“相當地”復雜。

                   比對一下Java平臺與.NET平臺,往往會發現在許多地方Java封裝得較少。

                   從應用程序開發角度來看,不少地方Java在使用上不如.NET方便。就拿本文所涉及的非常常見的整數類型及其運算而言,相信大家都看到了,使用Java編程需要留心這個“Intege對象緩存r”的陷阱,而.NET則很貼心地把這些已發現的陷阱(.NET設計者說:當然肯定會有沒發現的陷阱,但那就不關我事了)都蓋上了“厚厚”的井蓋,讓開發者很省心,因而帶來了較高的開發效率和較好的開發體驗。

                   但另一方面,JavaJDK代碼一覽無余,是開放的,你要探索其技術內幕,總是很方便,這點還是比較讓人放心。

                   .NET則相對比較封閉,總是遮遮掩掩,想一覽其廬山真相還真不容易,而且我感覺它為開發者考慮得太周到了,服務得太好了,這不見得是一件好事。因為人性的弱點之一就是“好逸惡勞”,生活太舒服了,進取精神就會少掉不少,.NET開 發者很容易于不知不覺中養成了對技術不求甚解的“惡習”,因為既然代碼能夠正常工作,那又何必費心地去追根問底?但話又說回來,如果僅滿足于知其然,又怎 會在技術上有所進步和提高?等到年紀一大,就被年輕人給淘汰了。而這種現象的出現,到底應該怪微軟,怪周遭的環境,還是自己呢?

          1 奇特的程序輸出

                   前段時間,一個學生給我看了一段“非常詭異”的Java代碼:

           

          public class TestInteger {

              public static void main(String[] args){

                  Integer v1=100;

                  Integer v2=100;

                    System.out.println(v1==v2);  //輸出:true

                  Integer w1=200;

                  Integer w2=200;

                    System.out.println(w1==w2);  //輸出:false

              }

          }

           

                   讓這個學生最困惑的是,為什么這些如此相似的代碼會有這樣令人意外的輸出?

           

                   我平時多使用C#Java用得不多,初看到這段代碼的輸出,我也同樣非常奇怪:怎么會這樣呢?100200這兩個整型數值對Integer這個類有本質上的差別嗎?

           

                   為了弄明白出現上述現象的底層原因,我使用javap工具反匯編了Java編譯器生成的.class文件:

           

           

                

              通過仔細閱讀Java編譯器生的字節碼,我發現以下給Integer變量賦值的語句:

           

                   Integer v1=100;

           

                   實際上調用的是Integer.valueOf方法。

           

                   而完成兩個Integer變量比較的以下語句:

           

                   System.Console.WriteLine(v1 == v2);

           

                   實際生成的是if_acmpne指令。其中的a代表“address”,cmp代表“Compare”,ne代表“not equal”。

           

                   這條指令的含義是:比較Java方法棧中的兩個操作數(即v1v2),看看它們是不是指向堆中的同一個對象。

           

                   當給v1v2賦值100時,它們將引用同一個Integer對象。

           

           

                   那為什么當值改為200時,v1v2就“翻臉了”,分別引用不同的Integer對象?

           

                   秘密就在于Integer.valueOf方法。幸運的是,Java的類庫是開源的,所以我們可以毫不費力地看到相關的源代碼:

           

          public static Integer valueOf(int i) {

                  if(i >= -128 && i <= IntegerCache.high)

                      return IntegerCache.cache[i + 128];

                  else

                      return new Integer(i);

              }

           

                   一切真相大白,原來Integer在內部使用了一個私有的靜態類IntegerCache,此類內部封裝了一個Integer對象的cache數組來緩存Integer對象,其代碼如下:

           

                   private static class IntegerCache {

                          static final Integer cache[];

                          //……

                   }

           

                   再仔細看看IntegerCache內部的代碼,會看到它使用靜態初始化塊在cache數組中保存了[-128,127]區間內的一共256Integer對象。

           

                   當給Integer變量直接賦整數值時,如果這個數值位于[-128,127]內,JVMJava Virtual Machine)就直接使用cache中緩存的Integer對象,否則,JVM會重新創建一個Integer對象。

           

                   一切真相大白。

           

          2 進一步探索Integer

           

              我們再進一步地看看這個有趣的Integer

           

                   Integer v1=500;

                   Integer v2=300;

                   Integer addResult=v1+v2;  //結果:800

                   double divResult=(double)v1/v2;      //結果:1.6666666666666667

           

                   喲,居然Integer對象支持加減乘除運算耶!它是怎么做到的?

                   再次使用javap反匯編.class文件,不難發現:

                   Integer類的內部有一個私有int類型的字段value,它代表了Integer對象所“封裝”的整數值。

           

                   private final int value;

           

                   當需要執行v1+v2時,JVM會調用v1v2兩個Integer對象的intValue方法取出其內部所封裝的整數值value,然后調用JVM直接支持的iadd指令將這兩個整數直接相加,結果送回方法棧中,然后調用Integer.valueOf方法轉換為Integer對象,讓addResult變量引用這一對象。

                   除法則復雜一點,JVM先調用i2d指令將int轉換為double,然后再調用ddiv指令完成浮點數相除的工作。

                   通過上述分析,我們可以知道,其實Integer類本身并不支持加減乘除,而是由Java編譯器將這些加減乘除的語句轉換為JVM可以直接執行的字節碼指令(比如本例中用到的iaddddiv),其中會添加許多條用于類型轉換的語句。

                   由此可見,與原始數據類型int相比,使用Integer對象直接進行加減乘除會帶來較低的運行性能,應避免使用。

           

          3 JDKInteger類的“彎彎繞”設計方案

           

                   現在,我們站在一個更高的角度,探討一下Integer的設計。

                   我個人認為,給Integer類型添加一個“對象緩沖”不是一個好的設計,從最前面的示例代碼大家一定會感到這一設計給應用層的代碼帶來了一定的混亂。另外,我們看到JDK設計者只緩存了[-128,127]256Integer對象,他可能認為這個區間內的整數是最常用的,所以應該緩存以提升性能。就我來看,這未免有點過于“自以為是”了,說這個區間內的Integer對象用得最多有什么依據?對于那些經常處理>128的整數值的應用程序而言,這個緩存一點用處也沒有,是個累贅。就算真要緩存,那也最好由應用程序開發者自己來實現,因為他可以依據自己開發的實際情況緩存真正用到的對象,而不需背著這個包容著256Integer對象的大包袱。

                   而且前面也看到了,基于Integer對象的加減乘除會增加許多不必要的類型轉換指令,遠不如直接使用原始數據類型更快捷更可靠。

                   其實上用得最多的不是Integer對象而是它所封裝的一堆靜態方法(這些方法提供了諸如類型轉換等常用功能),我很懷疑在實際開發中有多少場合需要去創建大量的Integer對象,而且還假設它們封裝的數值還位于[-128,127]區間之內?

                 緩存Integer對象還對多線程應用程序帶來了一定的風險,因為可能會有多個線程同時存取同一個緩存了的Integer對象。不過JDK設計者已經考慮到了這個問題,我看到Integer類的字段都是final的,不可改,是一個不可變類,所以可以在多線程環境下安全地訪問。盡管在使用上沒問題,但這一切是不是有點彎彎繞?去掉這個對象緩存,Integer類型是不是“更輕爽”“更好用”?

           

          4  C# int挑戰Java Integer

           

                   Java的設計與.NET(以C#為例)的設計作個比較是有趣的。

                   Java將數據類型分為“原始數據類型”和“引用數據類型”兩大類,int是原始數據類型,為了向開發者提供一些常用的功能(比如將String轉換為int),所以JDK提供了一個引用類型Integer,封裝這些功能。

                   .NET則不一樣,它的數據類型分為“值類型”和“引用數據類型”兩大類,int屬于值類型,本身就擁有豐富的方法,請看以下C#代碼:

           

                             int i = 100;

                             string str = i.ToString();  //int變量本身就擁有“一堆”的方法

           

                   使用.NET的反匯編器ildasm查看一下上述代碼生成的IL指令,不難發現C#編譯器會將int類型映射為System.Int32結構:

           

           

           

           

                   注意System.Int32是一個值類型,生成于線程堆棧中,一般來說,在多線程環境下,使用值類型的變量往往比引用類型的變量更安全,因為它減少了多線程訪問同一對象所帶來的問題。

           

          ===============================

                   簡要解釋一下:請對比以下兩個方法:

           

                   void DoSomethingWithValueType(int value);

                   void DoSomethingWithReferenceType(MyClass obj);

           

                   當多個線程同時執行上述兩個方法時,線程函數使用值類型的參數value是比較安全的,不用擔心多個線程互相影響,但引用類型的obj參數就要小心了,如果多個線程接收到的obj參數有可能引用同一個MyClass對象,為保證運行結果的正確,有可能需要給此對象加鎖。

          =================================

                  

          posted on 2010-10-27 16:39 墻頭草 閱讀(317) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          人人游戲網 軟件開發網 貨運專家
          主站蜘蛛池模板: 潮州市| 莱阳市| 肃宁县| 弋阳县| 墨脱县| 谢通门县| 安徽省| 定兴县| 大安市| 禄丰县| 剑河县| 辽源市| 万山特区| 东台市| 逊克县| 治县。| 义马市| 尉犁县| 海南省| 康保县| 田林县| 皋兰县| 甘南县| 嘉兴市| 长白| 司法| 商河县| 荃湾区| 怀来县| 保定市| 宿州市| 平阳县| 茌平县| 贵溪市| 绥棱县| 临猗县| 迁西县| 哈巴河县| 永兴县| 临武县| 平昌县|