Java複習筆記第二篇--集合學習之實現原理二

weixin_34037977發表於2019-01-19

引言:上篇文章中我們學習了List和Set介面下面的集合的實現原理,這篇文章我們主要來學習Map介面下面的各個集合的實現原理。

2853119-0025f273f9531746.png
image.png

1、HashMap

1.1、特性
  • 採用鍵值對儲存
  • 非執行緒安全的集合
  • 允許值為null,只允許一個鍵為null
  • 可以通過Collections.synchronizedMap將其變為執行緒安全的集合,或者使用ConcurrentHashMap代替。
  • 初始容量為16,初始載入因子為0.75
1.2、實現原理

hash作為程式設計師使用最多的一種鍵值式的儲存集合;它的底層是通過陣列+連結串列+紅黑樹(JDK8優化之後);它根據鍵的hashCode值儲存資料,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。HashMap非執行緒安全,即任一時刻可以有多個執行緒同時寫HashMap,可能會導致資料的不一致。如果需要滿足執行緒安全,可以用 Collections的synchronizedMap方法使HashMap具有執行緒安全的能力,或者使用ConcurrentHashMap。

  • a、hashMap 的例項有兩個引數影響其效能:初始容量和載入因子
  • b、儲存資料的格式:通過一個靜態內部類Entry儲存資料;


    2853119-2a0debc4a5c242f1.jpg
    hashMap的儲存形式.jpg
static class Entry<K,V> implements Map.Entry<K,V>{
}
  • c、載入因子:預設為0.75;
    主要在擴容時起作用;一般也是擴容為原來的2倍
 void resize(int newCapacity) {
        Entry[] oldTable = table;
      //獲取擴容之前的容量
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        //將原來陣列的元素拷貝到新的元素中;
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
  • d、關於hash函式:
    hashMap在儲存資料時會先根據它的key值呼叫一次hash函式;將得到的值作為真實的儲存資料的鍵;當然不排除兩個不同的key值產生相同的鍵值情況;我們稱之為hash衝突;常見的解決hash衝突的方式主要有如下:
    • 拉鍊法:將衝突的節點組織成一條連結串列
    • 二次hash法:採用另外的雜湊函式對衝突結果進行處理的方法
    • 開發地址法:
      • 線性探測:從衝突的位置依次向下移動;
        D = H(key);
        ND = (D+di)%m; di取1,2,3,……,m-1
      • 二次探測:
        D = H(key);
        ND = (D+di)%m; di取11,-11,22,-22,……,KK,-KK  (K≤m/2)
      • 雙雜湊法
        D = H1(key);
        p = H2(key);
        ND = (D+p)%m;
int hash = hash(key);

和之前一樣,為了方便深入理解hashmap這個資料結構,我們還是針對它的新增元素和刪除元素的方法進行詳細探討:
插入元素:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
 }
 /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
2853119-9f10f603335d6616.png
HashMap的put函式.png

①.判斷鍵值對陣列table[i]是否為空或為null,否則執行resize()進行擴容;

②.根據鍵值key計算hash值得到插入的陣列索引i,如果table[i]==null,直接新建節點新增,轉向⑥,如果table[i]不為空,轉向③;

③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裡的相同指的是hashCode以及equals;

④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;

⑤.遍歷table[i],判斷連結串列長度是否大於8,大於8的話把連結串列轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行連結串列的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;

⑥.插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。

刪除元素

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
 /**
     * Implements Map.remove and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
    //判斷連結串列對應的位置是否為null        
    if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
      //是否已經形成連結串列
            else if ((e = p.next) != null) {
//是否已經行為樹形結構
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

2、TreeMap

2.1、特性
  • 插入的元素不能重複
  • 插入的元素預設按照key有序(預設按照升序)
2.2、實現原理

底層通過紅黑樹實現的,

  static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left; // 左孩子
        Entry<K,V> right;//右孩子
        Entry<K,V> parent;// 父親節點
        boolean color = BLACK;

和之前一樣我們還是來看看TreeMap的新增元素和刪除元素的方法來了解TreeMap的實現原理

新增元素:在樹種找到要插入的位置,即該節點的父親節點,然後根據comp的具體值決定是要插入到left還是right

    public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

刪除節點:本質上就是紅黑樹的節點刪除操作,想要理解TreeMap的刪除操作最好深入理解紅黑樹的刪除操作

    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

    /**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

參考文獻:
連結:https://zhuanlan.zhihu.com/p/21673805
http://alex09.iteye.com/blog/539549

相關文章