Source code analysis – HashMap

Time:2021-10-20

preface

High frequency use in daily workHashMapThis data structure, and I also encountered relevant interview questions in the last job search process. Tonight, learn about the internal design of the data structure by reading the source code. JDK version 1.8.0_ two hundred and thirty-one

text

Inheritance relationship

Source code analysis - HashMap

HashMap inheritsAbstractMapClass, which implementsMap Cloneable SerializableInterface.

  • MapThe interface contains some common operation methods
  • CloneableIndicates that copies can be made
  • SerializableIndicates that serialization is implemented

Construction method

There are four construction methods for HashMap class

1.HashMap(int initialCapacity, float loadFactor)

/**
     *@ param initialCapacity initial capacity
     *@ param LoadFactor default value of load factor: 0.75
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //Throw exception when initial capacity is less than 0
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        //If the initial capacity is greater than the maximum value of 1 < < 30, set the maximum value as the capacity
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //Load factor less than 0 or throw exception for non numeric type
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
        this.loadFactor = loadFactor;
        //Get the critical value through the tablesizefor method
        this.threshold = tableSizeFor(initialCapacity);
    }
  1. HashMap(int initialCapacity)
//Call the constructor above to get the critical value
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

In AlibabaJava development manualThere are the following suggestions in the document:
Source code analysis - HashMap

  1. HashMap()
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }
  1. HashMap(Map<? extends K, ? extends V> m)
//Merge a map
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

Add element

By callingput()Method to add data,

//The hash () method is used to obtain the hash value corresponding to the key
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//When the key is null, it returns 0. Here you can see that HashMap supports null values;
    //When the key is not null, obtain the hashcode corresponding to the key and assign it to the variable H, and the variable H shifts 16 bits to the right; Then perform XOR operation and return the result
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

callputVal()Method to add elements

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> p;
        int n, i;
        //First, judge whether the table contains data. If there is no data, the first capacity expansion is required
        if ((tab = table) == null || (n = tab.length) == 0)
            //Call the resize () method to initialize
            n = (tab = resize()).length;
        //Modulo the hash code to obtain the position of the key. If P = = null, it means a new element
        if ((p = tab[i = (n - 1) & hash]) == null)

            //Insert the new element into the i-th position of the array
            tab[i] = newNode(hash, key, value, null);

            //Not a new element
        else {
            HashMap.Node<K, V> e;
            K k;
            //Judge whether the element exists through the equals () method. If it exists, replace it
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                //For example, judge whether it is a tree structure at this time
            else if (p instanceof TreeNode)
                e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else {
                //If this is a linked list structure, append the element to the end
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //If the length of the linked list is greater than or equal to 8, the linked list will be converted into a red black tree
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //Number of records modified
        ++modCount;
        //If the critical value is exceeded, expand the capacity
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

The single linked list structure is used to store data

static class Node<K, V> implements Map.Entry<K, V> {
        //Hash value represented by key
        final int hash;
        //Key
        final K key;
        //Value
        V value;
        //Point to the next element (single linked list)
        HashMap.Node<K, V> next;

        Node(int hash, K key, V value, HashMap.Node<K, V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final String toString() {
            return key + "=" + value;
        }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        //Judge whether the two keys are equal through equals() [in the previous interview, an interviewer mentioned this question]
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

Capacity expansion methodresize()

final HashMap.Node<K, V>[] resize() {
        //Table represents the array storing data. If the data is added for the first time, it is empty at this time. Otherwise, it is the data added before
        HashMap.Node<K, V>[] oldTab = table;
        //
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //Threshold indicates the critical value
        int oldThr = threshold;
        int newCap, newThr = 0;
        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;
        } else if (oldThr > 0)
            newCap = oldThr;
        else {
            //If the capacity is not specified when using HashMap and the data is added for the first time, the initialization of the array will continue here; At this time, the capacity is 16 and the critical value is 12
            //Default capacity 1 < < 4 = = 16
            newCap = DEFAULT_INITIAL_CAPACITY;
            //Critical value: 16 * 0.75
            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);
        }
        //Assign a new threshold value to threshold
        threshold = newThr;
        @SuppressWarnings({"rawtypes", "unchecked"})

        //Create a new array
                HashMap.Node<K, V>[] newTab = (HashMap.Node<K, V>[]) new HashMap.Node[newCap];
        table = newTab;

        //Data migration after capacity expansion 
        
        //If there is data in the old array, assign the data to the new array, and then set the values in the old array to null one by one
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                HashMap.Node<K, V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //E.next = = null expression has no hash conflict
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;

                        //A hash conflict has occurred

                        //If it's a red black tree
                    else if (e instanceof TreeNode)
                        ((HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                    else {
                        //Migrate the original data
                        HashMap.Node<K, V> loHead = null, loTail = null;
                        HashMap.Node<K, V> hiHead = null, hiTail = null;
                        HashMap.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;
    }

Find element

useget()Method to obtain the data in HashMap

//If the value retrieved by the getNode () method does not exist, null is returned, and the value obtained is returned
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

getNode()Use before getting datahash()Method to obtain the hash value corresponding to the key

final HashMap.Node<K, V> getNode(int hash, Object key) {
        HashMap.Node<K, V>[] tab;
        HashMap.Node<K, V> first, e;
        int n;
        K k;
        //Assign the data in the original array to tab, judge whether the length of the tab array is greater than 0, and see whether the location of the key in the array can be obtained without null. If the check fails, null will be returned
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {

            //Determine whether it is a hash conflicting element
            if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //If there is a hash conflict, the element will be added to the linked list, if next= Null indicates that it is a linked list structure and needs to be obtained from the linked list
            if ((e = first.next) != null) {
                //When the length of the linked list reaches 8, the linked list is converted to; At this time, judge whether it is a red black tree and obtain data from it
                if (first instanceof TreeNode)
                    return ((HashMap.TreeNode<K, V>) first).getTreeNode(hash, key);
                //Traverse the linked list and get data
                do {
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

summary

Through the above source code analysis, we can see that HashMap is composed of array, single linked list and red black tree.

  • Add elements: first, the elements are saved in the data in turn. If a hash conflict occurs at this time, a single linked list is created on the hash bit. When the length of the single linked list is greater than or equal to 8, the single linked list is converted into a red black tree
  • Find element: judge whether the hash value corresponding to the key exists in the array. If it exists, take it out. If the next attribute of the element is not null, judge whether it exists in the tree and linked list in turn, and then take it out. If it does not exist, return null

Source code analysis - HashMap

Read the original text

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]