1. 程式人生 > >深入解析 Java集合類ArrayList與Vector的區別

深入解析 Java集合類ArrayList與Vector的區別

集合類分為兩個分支,Collection與Map,其中Collection介面繼承了Iterator介面,繼承Iterator介面的類可以使用迭代器遍歷元素(即Collection介面的類都可以使用),今天我們從相同點、不同點、以及JDK原始碼等各個方面來深入解析下,底層使用陣列實現的兩個集合類:ArrayList與Vector的區別與聯絡

區別與聯絡:

1.ArrayList出現於jdk1.2,vector出現於1.0.兩者底層的資料儲存都使用的Object陣列實現,因為是陣列實現,所以具有查詢快(因為陣列的每個元素的首地址是可以得到的,陣列是0序的,所以: 被訪問元素的首地址=首地址+元素型別位元組數*下標    ),增刪慢(因為往陣列中間增刪元素時,會導致後面所有元素地址的改變)的特點

2.繼承的類實現的介面都是一樣的,都繼承了AbstractList類(繼承後可以使用迭代器遍歷),實現了RandomAccess(標記介面,標明實現該介面的list支援快速隨機訪問),cloneable介面(標識介面,合法呼叫clone方法),serializable(序列化標識介面)

3.當兩者容量不夠時,都會進行對Object陣列的擴容

(1)解析ArrayList擴容原始碼(假設從初始開始size=0,且構造方法為: new ArrayList<>();   ):

①首先呼叫add方法,新增元素,在add中呼叫ensureCapacityInternal(確保內部容量),將當前的size(實際元素數量)傳輸

②在ensureCapacityInternal中,首先將elementData陣列  與  DEFAULTCAPACITY_EMPTY_ELEMENTDATA  進行比較,這裡我們假設的構造方法為下圖,此時兩個陣列相等,minCapacity等於大的值,DEFAULT_CAPACOTY的值為10(在成員變數中定義),即minCapacity=10。

③modcount是在ArrayList的父類AbstractList中定義的成員變數,用於記錄修改次數(對當前ArrayList的修改次數),

minCapacity=10,element.length=0,所以執行grow方法。

④grow方法中由於初始容量為0,所以newCapatcoty=0,然後newCapacity=minCapacity等於10 (即通常說的:ArrayList的預設構造方法,會預設分配長度為10的記憶體空間,這裡的分配不是在建立物件時分配,而是在增加第一條資料的過程中分配,這樣防止了記憶體的浪費),然後進行Arrays.copyOf 。如果再次擴容的話,擴容到當前容量的1.5倍。

(2)解析Vector擴容原始碼

①首先呼叫add方法,與arraylist相同,vector也有一個繼承父類的成員變數modCount來記錄修改次數。

②如果當前實際元素數+1大於陣列定義長度,執行grow方法

③將elementData copy到 一個新的長度陣列中,完成gorw。  其中,  capacityIncrement為自定義的增長因子(此處與arrayList不同,arraylist預設增長1.5倍;vector可以自定義若不自定義,則增長2倍,若定義則新長度=之前的長度+增長因子)  MAX_ARRAY_SIZE為陣列定義的最大長度,如果是負數,則丟擲OutOfMemoryError異常,如果大於MAX_ARRAY_SIZE,則賦值為Int型別的最大值。  

4.構造方法略有不同。

ArrayList:

(1)ArrayList a1 = new ArrayList(int i); 指定初始化容量的構造方法

(2)ArrayList a2 = new ArrayList(); 預設構造方法,在新增第一個元素過程中初始化一個長度為10的Object陣列

(3) ArrayList a3 = new ArrayList(Collection); 在構造方法中新增集合,本方法建立的集合的object陣列長度等於實際元素個數

  

Vector:

(1)Vector v1 = new Vector(10,2); 指定初始長度(initialCapacity)與增長因子(capacityIncrement)注意這裡的增長因子不是oldCapacity * capacityIncrement而是+,如果不指定或者指定為0,則預設擴容當前容量的兩倍,這裡上面提過了

(2)Vector v2 = new Vector(10);  通過this關鍵字呼叫上面的構造方法,自定義初始陣列長度,增長因子預設為0

(3)Vector v3 = new Vector(); 預設構造方法,在建立物件時便分配長度為10的Object陣列。(這裡在建立時便分配記憶體,一定意義上,浪費了記憶體空間)

5.執行緒的安全性不同,vector是執行緒安全的,在vector的大多數方法都使用synchronized關鍵字修飾,arrayList是執行緒不安全的(可以通過Collections.synchronizedList()實現執行緒安全)

6.效能上的差別,由於vector的方法都有同步鎖,在方法執行時需要加鎖、解鎖,所以在執行過程中效率會低於ArrayList,另外,效能上的差別還體現在底層的Object陣列上

vector:arrayList:

可以看出來,arrayList多了一個transient關鍵字,這個關鍵字的作用是防止序列化,然後在ArrayList中重寫了了readObject和writeObject方法,這樣是為了在傳輸時提高效率,我們先來看下原始碼:

可以看到,這兩個方法中將elementData陣列中實際存在的元素遍歷出來進行傳輸,假設現在容量為10000但是實際有8000元素,如果將2000空的元素也進行傳輸勢必會影響效率,所以這麼做提高了效率,節省了時間。 

這兩個方法在序列化時如何被呼叫的,為什麼是private修飾?在傳輸時,ObjectInputStream與ObjectOutputStream會通過反射呼叫這兩個方法。  private修飾時因為,在ObjectStreamClass類中,呼叫的是傳輸物件中private修飾的writeObject與readObject(這裡就不深入研究了,光從找下面這個圖片的原始碼就可以感覺到,IO流的原始碼比集合類可能複雜的多)

defaultReadObject與dafaultWriteObject的作用?

如果類中不自定義readObject與writeObject,那麼類在序列化的時候會呼叫ObjectInputStream與ObjectOutputStream中的defaultReadObject與defaultWriteObject方法進行預設序列化,這樣的話,就不會序列化transiend中的陣列。但是transiend修飾的陣列是必須要序列化的! 如果自定義的話,就不會呼叫這兩個default方法,這樣的話類中所有需要序列化的都要自定義,這樣太麻煩了,所以在自定義的方法中先呼叫下他,將不是transiend的序列化,然後再自定義object陣列的序列化。

  最後再說一下,這兩個集合類如何在迭代時保證執行緒安全,這裡就要提一下上面說過的在AbstractList類中有一個靜態變數

modcount(我看網上一些帖子說modcount只存在於執行緒不安全的集合類中,其實這種說法是錯誤的,在vector中也使用了modcount用於保證迭代時資料安全)他用於記錄一個集合類物件被修改的次數。這兩個類在迭代時(呼叫iterator方法時),Iterator iterator = arrayList.iterator();或Iterator iterator2 = vector.iterator(); 返回的iterator物件都是類中的一個私有內部類。這個類在呼叫時,便初始化了一個expectedModCount=modcount,即在迭代前先用成員變數儲存下modcount的值。

在迭代時,首先會呼叫checkForComodification方法,來比較modCount的值有沒有被改變,如果改變則會丟擲異常,這樣就保證了迭代時的安全性(這裡的安全性不只是保證了多執行緒下的安全,也保證了單執行緒中迭代時,如果修改資料所造成的隱患)這就是所謂fail-fast策略(快速失敗策略)。

第一篇部落格就到這裡了,以後會陸續更新其他技術部落格,如果有什麼問題麻煩大家提出我將不勝感激,也歡迎大家一起探討java的相關知識,本部落格如果後續我還想到什麼問題,還會持續更新的。

最後送大家一段蘋果的廣告語,致瘋狂的人 

      他們特立獨行。他們桀驁不馴。他們惹是生非。他們格格不入。他們用與眾不同的眼光看待事物。他們不喜歡墨守成規。他們也不願安於現狀。你可以認同他們,反對他們,頌揚或是詆譭他們。但唯獨不能漠視他們。因為他們改變了尋常事物。他們推動人類向前邁進。或許他們是別人眼裡的瘋子,但他們卻是我們眼中的天才。因為只有那些瘋狂到以為自己能夠改變世界的人,才能真正改變世界。