HashMap source code analysis and interview questions

Table of contents

5. HashMap source code analysis

5.1. Initialize capacity

5.2 What is the load factor?

5.3. Can the load factor be greater or less than 0.75?

5.4. What is the expansion length?

5.5. How is the subscript calculated? 

5.6. How to resolve hash conflicts?

5.7. When is it converted to a tree?

5.8, HashMap data structure


5. HashMap source code analysis

5.1. Initialize capacity

Before the put method is performed, the capacity is 0, and after the put method, the default initial capacity will be assigned as 16.

In the source code, the initialization of the HashMap without parameter construction will assign a load factor of 0.75. But the capacity is not assigned and initialized.

Then the initial capacity is 0 at this time

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
public class HashMapDemo {
    public static void main(String[] args) {
        HashMap hashMap=new HashMap();
        //调用put方法
        hashMap.put("a","1");
    }
}

 put source code

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

At this time, a putVal method will be returned, which passes the hash value of the key, the key value and the value value.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab是一个数组,初始化为null,走第一个if语句
        if ((tab = table) == null || (n = tab.length) == 0)
            //这里调用了一个resize()方法,也就是扩容方法
            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;
    }

resize() expansion method 

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //HashMap初始化时oldTab为null,经过三目运算为0,那么不会走下面的if语句
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //最终会走这里的赋值,将newCap容量赋值为默认的容量16
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            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 = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                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;
    }

 It can be seen from the above source code that the initial capacity of HashMap is 0 before calling the put method, and it is 16 after using the put method.

5.2 What is the load factor?

0.75, also known as the expansion threshold

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

5.3. Can the load factor be greater or less than 0.75?

No, because when expanding, the capacity will be initialized by load factor *, the default is 16*0.75=12, that is to say, when the capacity is greater than 12, the original capacity will be expanded twice.

But when the load factor is less than 0.75, such as 0.5, then when 16*0.5=8, capacity expansion is required, which will lead to waste of resources.

If the load factor is greater than 0.75, such as 16*1=16, the number of queries and insertions will increase, and the performance will decrease.

5.4. What is the expansion length?

Each expansion is twice the original size

        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE; //2的30次方
                return oldTab;
            }
            //如果没有超过最大值,则扩大容量为原来的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 2的一次方 2倍
        }

5.5. How is the subscript calculated? 

(The length of the array - 1) performs bit operations with the hash value of the key

i = (n - 1) & hash

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;
        //计算HashMap的下标
        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;
    }

5.6. How to resolve hash conflicts?

Hash conflict means that in the underlying array of HashMap, in the newly added data, it is found that it has the same hash value as the previously existing data, but the key is different. A memory address in an array can only store one data, so how to store multiple data? When the hash values ​​of the two keys are the same, and the equals of the two keys is false, use a linked list or a red-black tree.


5.7. When is it converted to a tree?

               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;
                }

p.next will traverse from the index of 1 until it is 7, satisfy the condition of binCount>=TREEIFY_THRESHOLD-1, and try to convert it into a tree, because it is satisfied when the index is 7, that is When the length of the linked list is 8, so when the length of the linked list is greater than or equal to 8, it is not finished yet, because it is trying to convert it into a tree, so check the treeifBin method

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //如果数组为null或者数组长度小于64,则进行扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        //否则,就会转换为树
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
 static final int MIN_TREEIFY_CAPACITY = 64;

It can be seen that in the source code, when the length of the array is greater than or equal to 64, the following conversion number code will be executed.

Summary: When the length of the linked list is greater than or equal to 8 and the length of the array is greater than or equal to 64, the linked list will be converted into a tree.

5.8, HashMap data structure

Before jdk1.8, the underlying data structure of hashMap is: array + linked list

After jdk1.8, the data structure of hashMap is: array + linked list + red-black tree

Guess you like

Origin blog.csdn.net/select_myname/article/details/128293923