天天看點

碼出高效:Java開發手冊-第2章(12)

2.7 資料類型

2.7.1 基本資料類型

雖然Java 是面向對象程式設計語言,一切皆是對象,但是為了相容人類根深蒂固的資料處理習慣,加快正常資料的處理速度,提供了9 種基本資料類型,它們都不具備對象的特性,沒有屬性和行為。基本資料類型是指不可再分的原子資料類型,記憶體中直接存儲此類型的值,通過記憶體位址即可直接通路到資料,并且此記憶體區域隻能存放這種類型的值。Java 的9 種基本資料類型包括boolean、byte、char、short、int、long、float、double 和refVar。前8 種資料類型表示生活中的真假、字元、整數和小數,最後一種refVar 是面向對象世界中的引用變量,也叫引用句柄。本書認為它也是一種基本資料類型。前8 種都有相應的包裝資料類型,除char 的對應包裝類名為Character,int 為Integer 外,其他所有對應的包裝類名就是把首字母大寫即可。這8種基本資料類型的預設值、空間占用大小、表示範圍及對應的包裝類等資訊如表2-4所示。

預設值雖然都與0 有關,但是它們之間是存在差別的。比如,boolean 的預設值以0 表示的false,JVM 并沒有針對boolean 資料類型進行指派的專用位元組碼指令,boolean flag = false 就是用ICONST_0,即常數0 來進行指派;byte 的預設值以一個位元組的0 表示,在預設值的表示上使用了強制類型轉化;float 的預設值以單精度浮點數0.0f 表示,浮點數的0.0 使用字尾f 和d 差別辨別;char 的預設值隻能是單引号的'\u0000'表示NUL,注意不是null,它就是一個空的不可見字元,在碼表中是第一個,其碼值為0,與'\n' 換行之類的不可見控制符的了解角度是一樣的。注意,不可以用雙引号方式對char 進行指派,那是字元串的表示方式。在代碼中直接出現的沒有任何上下文的0 和0.0 分别預設為int 和double 類型,可以使用JDK10 的類型推斷證明:var a=0; Long b=a; 代碼編譯出錯,因為在自動裝箱時,0 預設是int 類型,自動裝箱為Integer,無法轉化為Long 類型。

表2-4 基本資料類型

碼出高效:Java開發手冊-第2章(12)

所有數值類型都是有符号的,最大值與最小值如表2-4 所示。因為浮點數無法表示零值,是以表示範圍分為兩個區間:正數區間和負數區間。表2-4 中的float 和double 的最小值與最大值均指正數區間,它們對應的包裝類并沒有緩存任何數值。

引用分成兩種資料類型:引用變量本身和引用指向的對象。為了強化這兩個概念的區分,本書把引用變量(Reference Variable)稱為refVar,而把引用指向的實際對象(Referred Object)簡稱為refObject。

refVar 是基本的資料類型,它的預設值是null,存儲refObject 的首位址,可以直接使用雙等号== 進行等值判斷。而平時使用refVar.hashCode() 傳回的值,隻是對象的某種哈希計算,可能與位址有關,與refVar 本身存儲的記憶體單元位址是兩回事。作為一個引用變量,不管它是指向包裝類、集合類、字元串類還是自定義類,refVar 均占用4B空間。注意它與真正對象refObject 之間的差別。無論refObject 是多麼小的對象,最小占用的存儲空間是12B(用于存儲基本資訊,稱為對象頭),但由于存儲空間配置設定必須是8B 的倍數,是以初始配置設定的空間至少是16B。

一個refVar 至多存儲一個refObject 的首位址,一個refObject 可以被多個refVar存儲下它的首位址,即一個堆内對象可以被多個refVar 引用指向。如果refObject 沒有被任何refVar 指向,那麼它遲早會被垃圾回收。而refVar 的記憶體釋放,與其他基本資料類型類似。

基本資料類型int 占用4 個位元組,而對應的包裝類Integer 執行個體對象占用16 個位元組。這裡可能會有人問:Integer 裡邊的代碼就隻占用16B ?這是因為字段屬性除成員屬性int value 外,其他的如MAX_VALUE、MIN_VALUE 等都是靜态成員變量,在類加載時就配置設定了記憶體,與執行個體對象容量無關。此外,類定義中的方法代碼不占用執行個體對象的任何空間。IntegerCache 是Integer 的靜态内部類,容量占用也與執行個體對象無關。由于refObject 對象的基礎大小是12B,再加上int 是4B,是以Integer 執行個體對象占用16B,按此推算Double 對象占用的存儲容量是24B,示例代碼如下:

class RefObjDemo {

// 對象頭最小占用空間12 個位元組(第1 處)

// 下方4 個byte 類型配置設定後,對象占用大小是16 個位元組

byte b1;

byte b2;

byte b3;

byte b4;

// 下方每個引用變量占用是4 個位元組,共20 個位元組

Object obj1;

Object obj2;

Object obj3;

Object obj4;

Object obj5;

// RefObjOther 執行個體占用空間并不計算在本對象内,依然隻計算引用變量大小4個位元組

RefObjOther o1 = new RefObjOther();

RefObjOther o2 = new RefObjOther();

// 綜上,RefObjDemo 對象占用:12B + (1B×4) + (4B×5) + (4B×2) = 44 個位元組

// 取8 的倍數為48 個位元組

}

class RefObjOther {

// double 類型占用8 個位元組,但此處是數組引用變量

// 是以對象頭12B + 4B = 16B,并非是8012 個位元組

// 這個數組引用的是double[] 類型,指向實際配置設定的數組空間首位址

// 在new 對象時,已經實際配置設定空間

double[] d = new double[1000];

}

在上述示例代碼中,第1 處提到的對象頭最小占用空間為12 個位元組,其内部存儲的是什麼資訊呢?下面來分析其内部結構,如圖2-10 所示,對象分為三塊存儲區域。

碼出高效:Java開發手冊-第2章(12)

圖2-10 對象頭的内部結構

(1)對象頭(Object Header)

對象頭占用12 個位元組,存儲内容包括對象标記(markOop)和類元資訊(klassOop)。對象标記存儲對象本身運作時的資料,如哈希碼、GC 标記、鎖資訊、線程關聯資訊等,這部分資料在64 位JVM 上占用8 個位元組,稱為“Mark Word”。為了存儲更多的狀态資訊,對象标記的存儲格式是非固定的(具體與JVM 的實作有關)。類元資訊存儲的是對象指向它的類中繼資料(即Klass)的首位址,占用4 個位元組,與refVar 開銷一緻。

(2)執行個體資料(Instance Data)

存儲本類對象的執行個體成員變量和所有可見的父類成員變量。如Integer 的執行個體成員隻有一個private int value,占用4 個位元組,是以加上對象頭為16 個位元組;再如,上述示例代碼的RefObjDemo 對象大小為48 個位元組,一個子類RefObjSon 繼承RefObjDemo,即使子類内部是空的,new RefObjSon 的對象也是占用48 個位元組。

(3)對齊填充(Padding)

對象的存儲空間配置設定機關是8 個位元組,如果一個占用大小為16 個位元組的對象,增加一個成員變量byte 類型,此時需要占用17 個位元組,但是也會配置設定24 個位元組進行對齊填充操作。