學習過程中的部分問題和集合源碼理解
一、問題
1,ArrayList, LinkedList, Vector 的異同
同: 三個類都實現了List接口, 存儲數據的特點(有序可重複)相同. 底層都是Object[]存儲的.
不同點:
ArrayList : 作爲List接口的主要實現類, 1.2, 執行效率高, 線程不安全;
Vector : 作爲List的古老實現類, since JDK1.0, List是1.2出現的; 線程安全的, 效率低.
LinkedList : 底層結構使用雙向鏈表存儲的. 對於頻繁的插入和刪除操作, 使用此類比ArrayList效率高.
2,集合Collection中存儲的如果是自定義類的對象, 需要自定義的方法
Collection: 需要重寫equals() 方法
List: 也需要重寫equals() 方法,
Set: HashSet, LinkedHashSet, 重寫equals(), hashCode();
TreeSet: 需要實現Comparable或者Comparator. 在使用構造器的時候會有一個TreeSet(Comparator comparator) 的構造方法. 在比較的時候使用, 它是使用二叉樹(紅黑樹)在底層進行存儲的.
3,Map結構的理解
Map : 雙列數據, 存儲key-value鍵值對, ----類似於高中的函數
HashMap: Map的主要實現類, 線程不安全, 效率高, 存儲null的key和value
LinkedHashMap: 保證在遍歷Map元素時候, 可以按照添加的順序實現遍歷
原因: 在原有的HashMap底層結構基礎上, 添加了一對指針, 指向前一個和後一個元素,
對於頻繁的遍歷操作, 此類執行效率高於HashMap.
TreeMap: 保證按照添加的key-value進行排序, 實現排序遍歷. 按照key來排序. 此時考慮key的自然能排序, 或者定製排序.
底層使用了紅黑樹.
Hashtable: 古老實現類: 作爲古老的實現類, 效率不高, 線程安全, 不能存儲null的key和value. 隨便哪個是null都不行.
Properties: 常用來處理配置文件. key和value都是String類型的.HashMap的底層: 數組+鏈表(JDK7之前的版本) 數組+鏈表+紅黑樹(JDK8)
Map中的 Key: 無序的, 不可重複的, 使用Set存儲所有的Key. —>Key所在的類要重寫equals()和hashCode(), 以HashMap爲例.
Map中的 Value: 無序的, 可重複的, 是喲個Collection的存儲所有的value —> Value所在的類要重寫equals()方法
Map中的 entry: 無需的, 不可重複的, 使用Set存儲entry
4,同步代碼塊中同步監視器和共享數據的理解
同步監視器: 俗稱鎖
1、任何對象都可以充當鎖
2、多個線程需要使用同一個鎖
共享數據: 多個線程共同操作的數據, 即爲共享數據
需要使用同步機制, 將操作共享數據的代碼包裹起來, 包裹的時候不可以多, 也不可以少
5,Set子類理解
HashSet : Set的主要實現類, 線程不安全.
LinkedHashSet : 作爲HashSet的子類, 遍歷其中的數據時, 可以按照添加的順序去遍歷, (和有序有區別)
TreeSet : 數據類型要相同, 我們要對這個進行排序. 可以按照添加的對象的指定屬性, 進行排序.
6,集合Collection中存儲的如果是自定義類的對象, 需要自定義的方法
Collection: 需要重寫equals() 方法
List: 也需要重寫equals() 方法,
Set: HashSet, LinkedHashSet, 重寫equals(), hashCode();
TreeSet: 需要實現Comparable或者Comparator. 在使用構造器的時候會有一個TreeSet(Comparator comparator) 的構造方法. 在比較的時候使用, 它是使用二叉樹(紅黑樹)在底層進行存儲的.
二、源碼
(1)List 類
1,ArrayList源碼理解(jdk7 和jdk8 稍有不同)
- jdk7
ArrayList list = new ArrayList(); // 底層創建了長度是10的Object[]數組, elementData
list.add(123) ;
// elementData[0] = 123;
…
list.add(11);
// 如果此次的添加導致底層elementData數組的容量不夠, 則擴容. 默認情況下, 擴容爲原來容量1.5倍, 同時需要將原有數組中的內容複製到現有數組中.
// 建議使用帶有參數的ArrayList的構造器
ArrayList list = new ArrayList(50);
有點像懶漢式單例模式 - jdk8 中的變化
ArrayList list = new ArrayList();
// 底層Object[] elementData 初始化爲{}, 沒有創建10的默認代碼.
list.add(123);
// 第一次調用add() 方法時, 底層才創建了長度爲10的數組, 並將元素放在了, elementData中. …
// 後來的方法執行和JDK7的幾乎沒有區別.
有點像餓漢式單例模式 - 總結:
JDK7中對象的創建類似於單例模式中的餓漢式, JDK8中的類似於單例模式中的懶漢式
2,LinkedList源碼理解
LinkedList list = new LinkedList(); // 內部聲明瞭一個Node類型的first&last默認爲null,
list.add(123); // 將123封裝到Node中, 創建了Node變量
// 其中Node定義爲
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;
}
}
體現了LinkedList的雙向鏈表的說法
3,Vector源碼理解
JDK7和JDK8中通過Vector()構造器創建對象時, 底層都創建了10個默認容量, 擴容的時候, 擴容兩倍.
(2)Map類
1,HashMap的源碼理解
- JDK7
-
HashMap map = new HashMap();
在實例化之後, 底層創建了長度16的一維數組Entry[] table.
…已經執行過put…
map.put(key0, value0);
// 首先, 計算key0的hashCode(), 根據此Hash值得到在entry數組中的存放位置. 如果此位置上的數據爲空, 此時的key0-value0添加成功. 如果此位置上不爲空. (一個或多個數據以鏈表的形式存在的), 比較當前的key…
如果key0的哈希值與已經存在的hash值都不相同, 則添加成功.
如果key0的哈希值, 與已經存在的哈希值相同, 繼續比較equals(), 返回false, 添加成功, 返回true, 使用value0替換相同key的value值. -
補充: 有數據的時候, key-value和原來的數據以鏈表的方式存儲. 在不斷的添加過程中, 會涉及到擴容問題. 當超出臨界值(threshold)的時候(並且要存放的位置非空的時候)就會擴容爲原來的2倍, 並將原有的數據複製過來.
-
-
JDK8
- new HashMap(), 底層沒有創建一個長度爲16的數組.
- JDK8底層的數組是Node[], 不是Entry
- 首次調用put方法時, 底層去創建長度爲16的數組.
- JDK7底層結構只有數組+鏈表, JDK8底層結構, 數據+鏈表+紅黑樹.
- 當數組的某一個索引位置上的元素以鏈表形式存在的數據個數大於8且當前數組的長度超過64,
- 此時此索引位置上的所有數據改爲使用紅黑樹存儲.
2,LinkedHashMap 的底層理解.
- 源碼中的Entry拓展了
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 能夠記錄添加的元素的先後順序.
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
三、總結
雖然現在JDK已經更新到JDK14了,可是大多數肯定還是在使用低版本的JDK,比如我今天才剛剛更新到JDK11 LST但是依然可以從以前的JDK的更新中獲得很大的收益。
學習不止,加油,tlxw