天天看點

【jdk1.8】HashMap源碼分析類的繼承關系類的成員變量類的構造函數重要的方法References

類的繼承關系

其中,Map接口中規定了map類的常用方法。比如

get(Object): V

put(K, V): V

isEmpty(): boolean

size(): int

remove(Objec): V

等。

Cloneable

Serializable

是辨別接口。

類的成員變量

/**
     * 預設的初始容量(數組長度),必須是2的幂。
     */
    static final int DEFAULT_INITIAL_CAPACITY =  << ; // aka 16

    /**
     * 最大的容量, 用來修正如果有更大的值被設定。必須是2的幂,而且小于等于1<<30.
     */
    static final int MAXIMUM_CAPACITY =  << ;

    /**
     * 預設的填充因子,如果目前size大于目前容量(數組長度)乘以填充因子就需要擴容,擴為原來容量的兩倍。
     */
    static final float DEFAULT_LOAD_FACTOR = f;

    /**
     * 當bin中的數量大于此值的時候,就要将list轉成紅黑樹。
     */
    static final int TREEIFY_THRESHOLD = ;

    /**
     * 如果經過resize或者元素被remove,而使得一個bin中的元素數量少于這個值之後就應該紅黑樹轉成list了。
     */
    static final int UNTREEIFY_THRESHOLD = ;

    /**樹的最小的容量,至少是 4 × TREEIFY_THRESHOLD = 32 然後為了避免(resizing 和 treeification thresholds) 設定成64
     */
    static final int MIN_TREEIFY_CAPACITY = ;

    /**
     * 哈希數組,總是2的幂。
     */
    transient Node<K,V>[] table;

    /**
     * 存放具體元素的集。
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 在整個map中存數的key-value對的數目。
     */
    transient int size;

    /**
     * 整個hashmap進行結構調整的次數。用來檢測并發修改異常(ConcurrentModificationException)。
     */
    transient int modCount;

    /**
     * 當size達到這個值時将要被resize。(threshold = capacity * load factor)
     */
    int threshold;

    /**
     * 哈希數組的填充因子。
     */
    final float loadFactor;
           

類的構造函數

public HashMap(int initialCapacity, float loadFactor) {
        //初始容量不能小于零
        if (initialCapacity < )
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //初始容量不能大于最大容量
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //填充因子不能小于等于零或者非數字
        if (loadFactor <=  || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //進行初始化指派
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
           

其中

static final int tableSizeFor(int cap)

方法傳回大于給定cap的最小2次幂的數值。

static final int tableSizeFor(int cap) {
        int n = cap - ;
        n |= n >>> ;
        n |= n >>> ;
        n |= n >>> ;
        n |= n >>> ;
        n |= n >>> ;
        return (n < ) ?  : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + ;
    }
           
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
           
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
           
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
           

其中

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict)

函數就是将m中的每個元素放入現在的hashmap中。

【jdk1.8】HashMap源碼分析類的繼承關系類的成員變量類的構造函數重要的方法References

hashmap實作原理圖

圖檔來源:http://www.cnblogs.com/leesf456/p/5242233.html

重要的方法

static final int hash(Object key)

注意,hashmap的hash值是由此方法計算的。而hashtable的hash值直接使用key.hashCode()。

static final int hash(Object key) {
        int h;
        return (key == null) ?  : (h = key.hashCode()) ^ (h >>> );
    }
           

public V put(K key, V value)

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果table為空指針或者table長度為零,則resize擴容。
        if ((tab = table) == null || (n = tab.length) == )
            n = (tab = resize()).length;
        //如果數組中該哈希位為空,也就是該bin中還沒有元素,則生成新節點放入該位置。此時還是在數組中。
        //注意使用 (n - 1) & hash 來進行散列避免使用取餘(%)的開銷。 
        if ((p = tab[i = (n - ) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //若數組中該哈希位已經有元素。
            Node<K,V> e; K k;
            //如果将插入的key的哈希值和該位置的key的哈希值相等,并且key也相等。(相當于插入重複元素了)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //用e來記錄該節點
                e = p;
            //如果該節點是樹節點的類型
            else if (p instanceof TreeNode)
                //放入樹中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //不然,在連結清單的最末尾插入該節點
                for (int binCount = ; ; ++binCount) {
                    //此時達到連結清單最末尾
                    if ((e = p.next) == null) {
                        //生成新的節點插到連結清單最末尾
                        p.next = newNode(hash, key, value, null);
                        //插完之後如果該bin中的節點數過大,則應該将連結清單轉成樹形結構
                        if (binCount >= TREEIFY_THRESHOLD - ) // -1 for 1st
                            treeifyBin(tab, hash);
                        //put成功,跳出
                        break;
                    }
                    //如果在連結清單中找到了重複的key值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //與循環剛開始的 e = p.next 配合來周遊連結清單
                    p = e;
                }
            }
            //找到了重複的key
            if (e != null) { // existing mapping for key
                //儲存舊值
                V oldValue = e.value;
                //若非僅不存在則插入
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //讓LinkedHashMap回調
                afterNodeAccess(e);
                //傳回舊值
                return oldValue;
            }
        }
        //結構修改次數增加
        ++modCount;
        //如果節點數過多,則擴容
        if (++size > threshold)
            resize();
        //讓LinkedHashMap回調
        afterNodeInsertion(evict);
        return null;
    }

        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            //得到根節點
            TreeNode<K,V> root = (parent != null) ? root() : this;
            //周遊樹的節點
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //待插入節點key的哈希值比目前key的哈希值小,方向為左子樹
                if ((ph = p.hash) > h)
                    dir = -;
                //大于目前,右子樹
                else if (ph < h)
                    dir = ;
                //找到重複的key,傳回目前節點
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                //若hash值等于目前,而且調用compareComparables方法還是傳回相等
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == ) {
                    //還未搜尋過
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        //隻搜尋一次
                        searched = true;
                        //在根節點為ch的子樹中尋找key,找到則傳回
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    //方法用盡,隻能———比類名和System.identityHashCode值
                    dir = tieBreakOrder(k, pk);
                }
                TreeNode<K,V> xp = p;
                //根據direction選擇左右子樹,找到葉子節點進行插入
                if ((p = (dir <= ) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= )
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    //兼顧樹的指針與連結清單的指針
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }
           

public V get(Object key)

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //如果bin中的第一個節點不為空
        if ((tab = table) != null && (n = tab.length) >  &&
            (first = tab[(n - ) & hash]) != null) {
            //如果第一個key就已經是了,就傳回
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //不然,如果下一個節點非空
            if ((e = first.next) != null) {
                //如果是樹的節點類型,則在樹裡面找
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //否則周遊整個連結清單
                do {
                    //找到則傳回
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

        final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
            TreeNode<K,V> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                //待尋找節點hash值比目前節點key的hash值小
                if ((ph = p.hash) > h)
                    //在左子樹中找
                    p = pl;
                //若大,則在右子樹中找
                else if (ph < h)
                    p = pr;
                //若相等做進一步判斷
                //如果就是目前key,則傳回目前節點
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //若相等,還不是目前節點
                //左子樹為空,隻能在右子樹中找
                else if (pl == null)
                    p = pr;
                //隻能在右子樹中找
                else if (pr == null)
                    p = pl;
                //當相等,還不是目前節點,而且目前節點左右子樹都不空
                //若調用compareComparables方法能判斷出下一步的尋找方向
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != )
                    p = (dir < ) ? pl : pr;
                //若還是不行的話,則在右子樹中找,找到傳回
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                //上面所有步驟都不行的話,就讓無論如何都在左子樹中找吧
                else
                    p = pl;
            } while (p != null);
            //沒轍了,傳回空
            return null;
        }
           

這麼重要怎能沒有resize呢?

final Node<K,V>[] resize()

final Node<K,V>[] resize() {
        //儲存目前數組
        Node<K,V>[] oldTab = table;
        //儲存數組長度
        int oldCap = (oldTab == null) ?  : oldTab.length;
        //儲存門檻值
        int oldThr = threshold;
        int newCap, newThr = ;
        //如果以前數組有東西
        if (oldCap > ) {
            //之前的數組大于最大容量了,就不再擴容了
            if (oldCap >= MAXIMUM_CAPACITY) {
                //更改完門檻值之後就傳回
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //最一般的情況,新的數組長度為老數組長度的2倍
            else if ((newCap = oldCap << ) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //門檻值也翻倍
                newThr = oldThr << ; // double threshold
        }
        //如果以前數組裡沒東西,但是以前門檻值是被指派過的
        else if (oldThr > ) // initial capacity was placed in threshold
            //則初始容量被指派為門檻值
            newCap = oldThr;
        //如果數組也未分貝,門檻值也未曾指派
        //使用預設值(如使用HashMap()構造函數,之後再插入一個元素會調用resize函數,會進入這一步)
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //新門檻值為零
        if (newThr == ) {
            //門檻值為 capacity × loadFactor
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        //為數組申請空間
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //如果之前數組初始化過
        if (oldTab != null) {
            //周遊數組
            for (int j = ; j < oldCap; ++j) {
                Node<K,V> e;
                //目前下标的元素不為空
                if ((e = oldTab[j]) != null) {
                    //置空
                    oldTab[j] = null;
                    //就剩這一個節點
                    if (e.next == null)
                        newTab[e.hash & (newCap - )] = e;
                    //如果目前節點是樹節點類型
                    else if (e instanceof TreeNode)
                        //将樹拆分
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //連結清單類型
                    else { // preserve order
                        //lo == low, hi == high。由于數組翻倍了,相當于低位是老的數組,高位是新的一半。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //将同一bin中的元素按照hash值擴容之後新容量的高位的末位是否為0來判斷是否分割,完成rehash
                            if ((e.hash & oldCap) == ) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //低位
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //高位(新數組高一半的位置)
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        //傳回新數組
        return newTab;
    }
           
【jdk1.8】HashMap源碼分析類的繼承關系類的成員變量類的構造函數重要的方法References

關于上面所說的“高位”和“低位”

圖檔來源同上。

References

JDK1.8源碼分析之HashMap(一)

繼續閱讀