學習過程中的部分問題和集合源碼理解


一、問題

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


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