引言
先簡述Java中的集合結構,分爲兩類:單列集合Collection和雙列集合Set
單列集合Collection:有序可重複,一般用來代替數組,稱作可變數組
雙列集合Map:
ArrayList、Vector、和LinkedList的區別
ArrayList:作爲List接口的主要實現類之一,線程不安全,效率高,底層使用Object[] elementData存儲。
JDK7:
ArrayList list = new ArrayList();//底層創建了長度是10的Object[]數組elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加導致底層elementData數組容量不夠,則擴容。
默認情況下,擴容爲原來的容量的1.5倍,同時需要將原有數組中的數據複製到新的數組中。
JDK8:
ArrayList list = new ArrayList();//底層Object[] elementData初始化爲{}.並沒創建長度爲10的數組
list.add(123);//第一次調用add()時,底層才創建了長度10的數組,並將數據123添加到elementData[0]
後續的添加和擴容操作與jdk 7 無異。
總結小結:jdk7中的ArrayList的對象的創建類似於單例的餓漢式,而jdk8中的ArrayList的對象的創建類似於單例的懶漢式,延遲了數組的創建,節省內存。
LinkedList:對於頻繁的插入、刪除操作,使用此類效率比ArrayList高;底層使用雙向鏈表存儲。
LinkedList list = new LinkedList(); 內部聲明瞭Node類型的first和last屬性,默認值爲null
list.add(123);//將123封裝到Node中,創建了Node對象。
其中,Node定義爲:體現了LinkedList的雙向鏈表的說法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector:作爲List接口的古老實現類;線程安全的,效率低;底層使用Object[] elementData存儲
jdk7和jdk8中通過Vector()構造器創建對象時,底層都創建了長度爲10的數組。
在擴容方面,默認擴容爲原來的數組長度的2倍。
HashSet
HashSet底層依然使用數組和鏈表存儲,主要體現在無序性和不可重複性
向HashSet中add元素a時,先調用a元素的類hashCode()方法計算元素a的hash值,將這個hash值通過某種算法散列算法計算一個數組中位置i,當數組中i位置不存在元素時,則a元素添加成功;如果i位置存在元素b時,比較b的hash值和a的hash值,不一樣就使用鏈表形式將a,b存在i位置;如果hash值相同就比較equals方法,相同就不存儲,不同就鏈表方式存儲。
HashMap
JDK7
HashMap map = new HashMap():在實例化以後,底層創建了長度是16的一維數組Entry[] table。
...可能已經執行過多次put操作...
map.put(key1,value1):首先,調用key1所在類的hashCode()計算key1哈希值,此哈希值經過某種算法計算以後,得到在Entry數組中的存放位置。如果此位置上的數據爲空,此時的key1-value1添加成功。 ----情況。如果此位置上的數據不爲空,(意味着此位置上存在一個或多個數據(以鏈表形式存在)),比較key1和已經存在的一個或多個數據的哈希值:如果key1的哈希值與已經存在的數據的哈希值都不相同,此時key1-value1添加成功。----情況2。如果key1的哈希值和已經存在的某一個數據(key2-value2)的哈希值相同(哈希衝突),繼續比較:調用key1所在類的equals(key2)方法,比較:如果equals()返回false:此時key1-value1添加成功。----情況3。如果equals()返回true:使用value1替換value2。補充:關於情況2和情況3:此時key1-value1和原來的數據以鏈表的方式存儲。
在不斷的添加過程中,會涉及到擴容問題,當超出臨界值(且要存放的位置非空)時,擴容。默認的擴容方式:擴容爲原來容量的2倍,並將原的數據複製過來。
HashMap在jdk8中相較於jdk7在底層實現方面的不同:
1. new HashMap():底層沒創建一個長度爲16的數組
2. jdk 8底層的數組是:Node[],而非Entry[]
3. 首次調用put()方法時,底層創建長度爲16的數組
4. jdk7底層結構只:數組+鏈表。jdk8中底層結構:數組+鏈表+紅黑樹。
4.1 形成鏈表時,七上八下(jdk7:新的元素指向舊的元素。jdk8:舊的元素指向新的元素)
4.2 當數組的某一個索引位置上的元素以鏈表形式存在的數據個數 > 8 且當前數組的長度 > 64時,此時此索引位置上的所有數據改爲使用紅黑樹存儲。
紅黑樹
爲了解決hash衝突的問題,即一個hash值下面掛着很長的鏈表,所以在jdk1.8中HashMap底層由數組、鏈表、紅黑樹構成,紅黑樹是一種平衡二叉查找樹,所謂“平衡”,就是說不存在某個子樹很長。
紅黑樹的幾大特點:
(1)節點要麼是紅色,要麼是黑色
(2)根節點必須是黑色
(3)葉子節點必須是黑色的Null節點
(4)每個紅色節點的2個子節點必須是黑色
(5)樹中任意一個節點到每個葉子節點的路徑中都包含相同數量的黑色節點
紅黑樹可以保證自平衡靠的三大法寶:左旋、右旋、變色
左旋:旋轉節點的右節點爲父節點,右節點的左子樹給旋轉節點當右子樹
右旋:旋轉節點的左節點成爲父節點,左節點的右子樹成爲旋轉節點的左子樹
變色: