碼出高效讀書筆記:基本數據類型、包裝類型及字符串

1、基本數據類型

基本數據類型是指不可再分的原子數據類型,內存中直接存儲此類型的值,通過內存地址即可直接訪問到數據,並且此內存區域只能存放這種類型的值。

一般來說Java有8中數據類型,分別爲byte、char、short、int、long、float、double、boolean,但是碼出高效中多給出了一種基本數據類型,即refvar,這是一種面向對象世界中的引用變量,也叫作引用句柄。

前8種基本數據類型都有對應的包裝數據類型,除char的對應包裝類名爲Character,int爲Integer外,其他的6種都是把首字母大寫即可。

這8種基本數據類型的默認值、空間佔用大小、表示範圍及對應的包裝類等信息如下表所示:

重點講一下refvar:

引用分成兩種數據類型,即引用變量本身和引用指向的對象。爲了強化這兩個概念的區分,碼出高效中把引用變量成爲refvar,而把引用指向的實際對象成爲refobj。

refvar是基本的數據類型,默認值是null,存儲refobj的首地址,可以直接使用雙等號==進行等值判斷。而平時使用refvar.hashCode()返回的值,只是對象的某種哈希計算,可能與地址有關。與refvar本身存儲的內存單元地址是兩回事。作爲一個引用變量,不管它是指向包裝類、集合類、字符串類還是自定義類,refvar均佔用4B空間。注意它與真正對象refobj之間的區別。無論refobj是多麼小的對象,最小佔用的存儲空間是12B(用於存儲基本信息,稱爲對象頭),但由於存儲空間分配必須是8B的倍數,所以初始分配的空間至少是16B。

一個refvar至多存儲一個refobj的首地址,一個refobj可以被多個refvar存儲下它的首地址,即一個堆內對象可以被多個refvar引用指向。如果refobj沒有被任何refvar指向,那麼它遲早會被垃圾回收。而refvar的內存釋放,與其他數據類型相似。

2、包裝類型

8種基本數據類型都有相應的包裝類,因爲Java的設計理念是一切皆是對象,在很多情況下,需要以對象的形式操作,比如使用hashCode()獲取哈希值,或者getClass()獲取類等。包裝類的存在解決了基本數據類型無法做到的事情:泛型類型參數、序列化、類型轉換、高頻區間數據緩存

高頻區間數據緩存非常的重要,我們都知道Integer會緩存-128-127之間的值,對於Integer var=?在-128-127之間的賦值,Integer對象由IntegerCache.cache產生,會複用已有的對象,這個區間的Integer值可以直接使用==進行判斷,但是這個區間之外的所有數據都會在堆上產生,並不會複用已有對象,這是一個大問題,因此推薦所有包裝類對象之間值的比較,全部使用equals()方法。

除了Float和Double外,其他包裝數據類型都會緩存,6個包裝類直接賦值時,就是調用對應包裝類的靜態工廠方法valueOf(),這就是俗稱的自動裝箱,如下面代碼所示:

public class Test{
    public static void main(String[] args){
        //Integer的自動裝箱:把一個int型的值直接賦給一個Integer類型的引用,調用的是Integer的valueOf()方法
        Integer integer = 10;

        //Integer的自動拆箱:調用的是Integer的intValue()方法
        int i = integer;
    }
}


//Integer的valueOf()方法源碼
@HotSpotIntrinsicCandidate
public static Integer valueof(int i){
       if(i >= IntegerCache.low && i <=IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
       return new Integer(i);
}

如上面的源碼所示,如果i在緩存區間以內,那麼直接返回緩存中的Integer對象,否則就會new一個新對象,要學會合理利用緩存,提升程序的性能。各個包裝類的緩存區間如下:

  • Boolean:使用靜態final變量定義,valueOf()就是返回這兩個靜態值
  • Byte:表示範圍是-128-127,全部緩存
  • Short:表示範圍是-32768-32767,緩存區間是-128-127
  • Character:表示範圍是0-65535,緩存區間是0-127
  • Long:表示範圍是[-2^63,2^63-1],緩存區間是-128-127
  • Integer:表示範圍是[-2^31,2^31-1],緩存區間是-128-127,它是唯一一個可以修改緩存區間的包裝類,在VM options加入參數-XX:AutoBoxCacheMax=7777,即可設置最大緩存值爲7777

碼出高效中作者推薦了以下幾點:

  1. 所有的POJO類屬性必須使用包裝數據類型。
  2. RPC方法的返回值和參數必須使用包裝數據類型。
  3. 所有的局部變量推薦使用基本數據類型。

看了別人的博客以後我發現還有一點需要注意,請看下面代碼:

public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

先想一下輸出結果是什麼。這裏需要注意的是,==兩邊如果是包裝類的引用的話,那麼比較的就是這兩個引用是否指向同一個對象,如果有一邊是表達式運算的話,那麼比較的就是數值!另外,對於包裝器類型,equals方法並不會進行類型轉換。明白了這2點之後,上面的輸出結果便一目瞭然,輸出結果如下:

true
false
true
true
true
false
true

3、字符串

字符串也是常用的數據類型,它在JVM中的地位並不比基本數據類型低,JVM對字符串也做了特殊處理。

字符串相關類型主要有三種:

  1. String
  2. StringBuffer
  3. StringBuilder

String是隻讀字符串,典型的immutable對象,對它的任何改動,其實都是創建一個新對象,再把引用指向該對象。String對象賦值操作後,會在常量池中進行緩存,如果下次申請創建對象時,緩存中已經存在,則直接返回響應引用給創建者。

StringBuffer則可以在原對象上進行修改,是線程安全的,JDK5中引入的StringBuilder與StringBuffer均繼承自AbstractStringBuilder,兩個子類的很多方法都是通過“super.方法()”的方式調用抽象父類中的方法,此抽象類在內部與String一樣,也是以字符數組的形式存儲字符串的。StringBuilder是非線程安全的,把是否需要進行多線程加鎖交給工程師決定,操作效率比StringBuffer高。線程安全的對象先產生是因爲計算機的發展總是從單線程到多線程,從單機到分佈式。

在非基本數據類型的對象中,String是僅支持直接相加操作的對象。這樣操作比較方便,但在循環體內,字符串的連接方式應該使用StringBuilder的append()方法進行擴展,像下面這種方式是不推薦的:

String str = "start"
for(int i = 0;i < 100;i++){
    str = str + "hello";
}

此段代碼的內部實現邏輯是每次循環都會new一個StringBuilder對象,然後進行append操作,最後通過toString方法返回String對象,不但造成了內存資源良妃,而且性能更差。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章