“This is the first day of my participation in the First Challenge 2022. For details: First Challenge 2022”

The birth of HashMap

1.1 an array

Array: a physically contiguous piece of storage of a certain size.

Benefits: Quickly find and modify contents according to subscripts.

Disadvantages: Size determination, cannot be modified. Adding new elements or deleting elements can be tricky.

Static initialization of an array

        // Array implementation 1:
        // Data type array name [] = {value, value... }
        String str[] = {"Mobile end"."Android"."iOS"};
        System.out.println(str.length);// Three elements
        for (int i = 0; i <str.length ; i++) {
            System.out.println(str[i]);
        }
        // Array:
        // Data type array name [] = new data type [] {value, value... }
        String str2[] =  new String[3];
        str2[0] = "Mobile end";
        str2[1] = "Android";
        str2[2] = "iOS";
        System.out.println(str2.length);// Three elements
        for (int i = 0; i <str2.length ; i++) {
            System.out.println(str2[i]);
        }

Copy the code

The result is the same.

"E:\Android\Android StudioO\jre\bin\java.exe".3Mobile Android iOSCopy the code

If I want to add two more elements (Java, Kotlin), then the array is not appropriate, and the order table appears.

Table 1.2 order

Sequential table: A linear table stored as an array that is physically contiguous, logically contiguous, and dynamically incremented. (such as: ArrayList)

Sequential tables are used much more frequently than data. Use certainly can use, let’s look at the source code.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess.Cloneable.java.io.Serializable
{
    transient Object[] elementData;// An array buffer that stores the elements of ArrayList.
    private int size;/ / the size of the ArrayList
    public ArrayList(a) {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }    
    public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }
    
    public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
    public void clear(a) {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0; }}Copy the code

And then you’ll notice that we’re still using arrays, but we’re just handling the array instead of doing it ourselves.

Original data:

New data:

Adding and deleting elements in a sequential list requires a large number of operations such as moving elements, so the linked list is generated.

1.3 list

Linked list: A linked list is a discontinuous, non-sequential storage structure on a physical storage cell. The logical order of data elements is achieved by connecting the Pointers in the list.

new

Change the pointer to element 1 from element 2 to element 4, and then to element 4 to element 2.

Remove elements

The pointer to element 1 points directly to element 3.

1.4 ArrayList vs. LinkedList

ArrayList(sequential list) :

  • Advantages: Quick search
  • Disadvantages: add and delete slowly

LinkedList (list)

  • Advantages: fast addition and deletion
  • Disadvantages: Slow search

So the question is when do you use an ArrayList? When is LinkedList used? Why is that?

So can you combine the advantages of sequential and linked lists? – the Hash table

1.5 the Hash table

A Hash table (also called a Hash table) is a data structure that is directly accessed based on Key values. That is, it accesses records by mapping key values to a location in the table to speed up lookups. This mapping function is called a hash function, and the array of records is called a hash table.

A hash table is essentially an array. You can access data directly according to a key value, fast search.

Second, the HashMap

A HashMap uses Node(implements map.Entry) arrays to store key-value pairs. Each key-value pair forms a Node entity. The Node class is actually a one-way linked list structure with a Next pointer that can be connected to the Next Node entity.

Note: The Entry in jdk1.8 HashMap is gone, replaced by Node, but Node also implements the Map.Entry interface, similar to the previous Entry.

The Node class

    static class Node<K.V> implements Map.Entry<K.V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

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

        public final K getKey(a)        { return key; }
        public final V getValue(a)      { return value; }
        public final String toString(a) { return key + "=" + value; }

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

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

        public final boolean equals(Object o) {... }}Copy the code

Hash, key, and value represent the data of this node, and next represents the data pointing to the next node.

  • Before JDK1.8, the underlying hash table is implemented by array + linked list, that is, the linked list is used to handle conflicts, and the linked list with the same hash value is stored in a linked list. However, when there are many elements in a bucket, that is, there are many elements with the same hash value, searching by key value is inefficient.

  • In JDK1.8, hash table storage is realized by array + linked list + red-black tree. When the length of the linked list exceeds the threshold (8), the linked list is converted to red-black tree, which greatly reduces the search time.

2.0

class HashMapTest {
    public static void main(String[] args) {
        / / create a HashMap
        HashMap<String,String> hashMap = new HashMap<>();
        // Add data (key-value)
        hashMap.put("name"."Handsome");
        hashMap.put("age"."20");
        hashMap.put("subject"."Android");
        hashMap.put(null."Empty Key");
        System.out.println("HashMap1:"+hashMap.toString());//{null= null, subject=Android, name= 0, age=20}
        // Add data (overwrite existing keys)
        hashMap.put("age"."26");
        System.out.println("HashMap2:"+hashMap.toString());//{null= null, subject=Android, name= id, age=26}
        // Get value based on key
        System.out.println("Key-null:"+hashMap.get(null));/ / short Key
        System.out.println("Key-subject:"+hashMap.get("subject"));//Android

        // Delete elements based on key
        System.out.println("Delete subject corresponding to :"+hashMap.remove("subject"));// Delete subject corresponding to Android
        // Delete elements according to key-value
        System.out.println("Remove the age."+hashMap.remove("age"."20"));// Delete age corresponding to 20:false
        System.out.println(hashMap.toString());//{null= null Key, name= new, age=26}
        System.out.println("Remove the age."+hashMap.remove("age"."26"));// Delete age corresponding to 20:true
        System.out.println("HashMap3:"+hashMap.toString());//{null= null, name= null}

        // Select * from key-value Set ();
        Set<Map.Entry<String,String>> entrySet= hashMap.entrySet();
        for (Map.Entry<String, String> strEntry : entrySet) {
            System.out.println("Traversal the Set:"+strEntry.getKey()+"-"+strEntry.getValue());
        }
        // Get the Set of keys
        Set<String> strKeySet= hashMap.keySet();
        for (String s : strKeySet) {
            // Get the value from key
            System.out.println("Traversal the KeySet set:"+s+"-"+hashMap.get(s));
        }
        // Get a Set of key-values. // Get an iterator
        Set<Map.Entry<String,String>> itEntrySet= hashMap.entrySet();
        Iterator iterator = itEntrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry map= (Map.Entry)iterator.next();
            System.out.println("Iterator:"+map.getKey()+"-"+map.getValue());
        }
        // There are other ways to iterate over data}}Copy the code

The results

"E:\Android...."
HashMap1:{null= empty Key, subject=Android, name= time, age=20}
HashMap2:{null= empty Key, subject=Android, name= time, age=26}
Key-nullKey-subject:Android delete subject Corresponding to Android delete age:false
{null= Empty Key, name= time, age=26} to delete the age:true
HashMap3:{null= null Key, name= 0}nullSelect * from KeySet; select * from KeySet;null- Empty Key iterates through the KeySet: name- Iterator:null- Empty Key Iterator: name- Process finished with exit code0
Copy the code

2.1 Construction method

    /** * Default load factor value */
    static final float DEFAULT_LOAD_FACTOR = 0.75 f;
    /** * The load factor of the hash table. * /
    final float loadFactor;
    
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    /** * Construct an empty HashMap with a default load factor (0.75). * /
    public HashMap(a) {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
Copy the code

HashMap has four constructors that initialize three parameters:

  • InitialCapacity: indicates the initialCapacity (16 by default).
  • LoadFactor Loads the factor (0.75 by default).
  • Threshold Threshold: indicates the maximum number of value pairs that a hashMap can contain. If the number exceeds the threshold, the value pairs need to be expanded.threshold=initialCapacity*loadFactor.

Load factor: By default, the array size is 16, so when the number of elements in the HashMap exceeds 16*0.75=12, the array size is doubled to 16*2=32, and the position of each element in the array is recalculated. Of course, this value is optional, but changing it is not recommended.

We usually use the parameterless constructor. With the HashMap created, let’s start storing the data.

2.2 the put ()

Add data:

  • Key can be null

  • Key is unique. If key exists, value content is overwritten

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

Here we hash the key before calling the putVal() method.

2.2.1 hash (key)

    /** * Computes key.hashcode () and hashes the high hash (XOR) to the low hash. Since the table uses a quadratic power mask, the set of hashes that change bits only above the current mask will always conflict. * Using trees to handle a lot of conflicts in bin, we simply xor some of the shifted bits in the cheapest way to reduce system losses, as well as merge the impact of the highest bits that would otherwise never be used for index calculations due to table boundaries (source). * /
    static final int hash(Object key) {
        int h;
        return (key == null)?0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    public int hashCode(a) {
        return identityHashCode(this);
    }

    static int identityHashCode(Object obj) {
        int lockWord = obj.shadow$_monitor_;
        final int lockWordStateMask = 0xC0000000;  // Top 2 bits.
        final int lockWordStateHash = 0x80000000;  // Top 2 bits are value 2 (kStateHash).
        final int lockWordHashMask = 0x0FFFFFFF;  // Low 28 bits.
        if ((lockWord & lockWordStateMask) == lockWordStateHash) {
            return lockWord & lockWordHashMask;
        }
        return identityHashCodeNative(obj);
    }
Copy the code
  • Returns 0 when key == null, which means that key can be set to NULL.
  • You can see from the identityHashCode() method that the key can be of any type and can become a hashCode of type int.

What is H >>> 16?

H is hashCode. H >>> 16 is used to extract the height of H 16, (>>> is unsigned right shift)

0000 0100 1011 0011  1101 1111 1110 0001

>>> 4

0000 0000 0100 1011 0011  1101 1111 1110

>>> 16 
 
0000 0000 0000 0000  0000 0100 1011 0011
Copy the code

The above two examples – one >>>4 and one >>>16 – can be used for comparison.

This returns a Hash value based on the Key. So let’s go ahead and look at putVal().

2.2.2 putVal ()

    /** * table initializes the array of Node types that store data when first used, and resizes as needed. * Length is equal to a power of 2. (0 in special cases), each element of the array = 1 singly linked list */
    transient Node<K,V>[] table;
    
    / * * *@paramThe hash value of the hash key *@paramThe key key *@paramValue Specifies the value * to be placed@paramOnlyIfAbsent If true, do not change the existing value *@paramIf evict is false, the table is in create mode. *@returnThe previous value, if not, returns null */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // Check whether the table is initialized
        if ((tab = table) == null || (n = tab.length) == 0)
            // Call the resize() method to initialize and assign
            n = (tab = resize()).length;
        // Get the subscript using hash, if the data is null
        if ((p = tab[i = (n - 1) & hash]) == null)
            // TAB [I] subscript has no value, create a new Node and assign it.
            tab[i] = newNode(hash, key, value, null);
        else {
             // TAB [I] index has data, collision occurred
            Node<K,V> e; K k;
            // Check that the hash value of TAB [I] is the same as the hash value of TAB [I], and the key value of TAB [I] is the same as the key value
            if(p.hash == hash && ((k = p.key) == key || (key ! =null && key.equals(k))))
                // Replace the original data directly
                e = p;
            else if (p instanceof TreeNode)// Determine the data structure is a red-black tree
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {// Data structures are linked lists
                for (int binCount = 0; ; ++binCount) {
                
                    // the next node of p is null, indicating that p is the last node
                    if ((e = p.next) == null) {
                        // Create a Node and insert it at the end of the list
                        p.next = newNode(hash, key, value, null);
                        // When the element >=8-1, the list becomes a tree (red-black tree)
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // Exit the loop if the key already exists in the list
                    if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k))))
                        break;
                    // Update p to point to the next node and continue traversalp = e; }}// If a key already exists in the list, change the value of the original key and return the old value
            if(e ! =null) {
                V oldValue = e.value;
                if(! onlyIfAbsent || oldValue ==null)
                    e.value = value;
                afterNodeAccess(e);// Method to call when replacing old values (null by default)
                return oldValue;
            }
        }
        ++modCount;// Number of changes
        // Determine whether to expand the map size based on the map value
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);// Method to be called on successful insertion (null by default)
        return null;
    }
Copy the code

2.2.3 the resize ()

    /** * Default initial capacity - must be a power of 2. * /
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    /** * Initialize or double the table size. If empty, the allocation is based on the initial capacity target saved in the field threshold. * Otherwise, because we use the quadratic extension */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null)?0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        // The table has been initialized and its capacity is greater than 0
        if (oldCap > 0) {
            //MAXIMUM_CAPACITY=1<<30=2 to the 30th power =1073741824
            if (oldCap >= MAXIMUM_CAPACITY) {
                // If the old capacity reaches the maximum value, the capacity is not expanded and the threshold is set to the maximum value
                // integer. MAX_VALUE=(1 << 31)-1=2 to the 31st power -1=2147483648-1
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // Double the size
        }
        else if (oldThr > 0) // Initialize the container =threshold
            newCap = oldThr;
        else { 
            // If threshold and table are not initialized, it is the first initialization
            // The first time I entered there was nothing so I initialized the container (16)
            newCap = DEFAULT_INITIAL_CAPACITY;
            / / 12 = 0.75 * 16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // If newThr is 0, calculate the threshold
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // Update the threshold
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // Update table data
        table = newTab;
        // If there is already data in the previous bucket, the hash value will change as the table capacity changes, and the subscript needs to be recalculated
        if(oldTab ! =null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // will specify that subscript data is not null
                if((e = oldTab[j]) ! =null) {
                    // Empty the specified subscript data
                    oldTab[j] = null;
                    // Specify only one subscript data
                    if (e.next == null)
                        // Store the data directly to the newly calculated hash index
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)/ / the red-black tree
                        // Split the nodes in the tree box into lower tree box and upper tree box. If it is now too small (<=6), the data structure is removed from the red-black tree and replaced with a linked list.
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { / / list
                        // Recalculate the hash value and regroup according to the new subscript
                        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;
                                elsehiTail.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;
    }
Copy the code

Therefore, the resize() method initializes the container and assigns a value to table, returning Node<K,V>[].

2.2.4 putTreeVal ()

        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;
            // Get the root nodeTreeNode<K,V> root = (parent ! =null)? root() :this;
            If there is no key conflict, store the object directly and return null object
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //dir: determines the location of the node
                if ((ph = p.hash) > h)// The hash value of the current node is greater than the hash value of the stored object
                    dir = -1;
                else if (ph < h)// The hash value of the current node is smaller than the hash value of the stored object
                    dir = 1;
                else if((pk = p.key) == k || (k ! =null && k.equals(pk)))// If the current node is equal to the hash value of the object and the key is the same, the current node is returned
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    // If you sort by the comparator instead of the hash value, then the comparator returns the value to decide to enter the left and right nodes
                    if(! searched) { TreeNode<K,V> q, ch; searched =true;
                        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;
                    }
                    dir = tieBreakOrder(k, pk);
                }
                
                TreeNode<K,V> xp = p;
                 // If the left or right node of p is empty, the storage location has been found
                if ((p = (dir <= 0)? p.left : p.right) ==null) {
                    Node<K,V> xpn = xp.next;
                    // Create a node
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    // Set the location according to dir
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if(xpn ! =null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    // Insertion tree, set red and black nodes, whether to rotate
                    MoveRootToFront resets the root node
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null; }}}Copy the code

2.3 the get ()

To get the data

This method returns the result:

  • Node (the Node);
  • null
    • The data corresponding to this key is NULL.
    • The key does not exist in the HashMap
    public V get(Object key) {
        Node<K,V> e;
        // Hash (key) : Computes the hash value based on the hashCode of the key, as in put.
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
Copy the code

GetNode () returns null if the node is null, or the value of the key if the node exists.

2.3.1 getNode ()

    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @returnNode, if not null */ is returned
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // Get the first element of the table based on the hash value.
        if((tab = table) ! =null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) ! =null) {
            // If the first node is the Key we are looking for, we return it directly
            if (first.hash == hash && // ((k = first.key) == key || (key ! =null && key.equals(k))))
                return first;
            // Get information about the next node
            if((e = first.next) ! =null) {
                // If the data structure is a red-black tree, get the node by getTreeNode() and return it
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // Otherwise loop through the list to find the node and return
                do {
                    if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k))))
                        return e;
                } while((e = e.next) ! =null); }}return null;
    }
Copy the code

2.3.2 getTreeNode ()

    static final class TreeNode<K.V> extends LinkedHashMap.LinkedHashMapEntry<K.V> {
        TreeNode<K,V> parent;  // Red black tree node
        // Call the tree's find() function
        final TreeNode<K,V> getTreeNode(int h, Object k) {
            return((parent ! =null)? root() :this).find(h, k, null);
        }
        
Copy the code

2.3.3 the find ()

        final TreeNode<K,V> find(inth, 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;
                if ((ph = p.hash) > h)// The current node hash value is greater than the given hash value, enter the left node
                    p = pl;
                else if (ph < h)// The current node is less than the given hash value, enter the right node
                    p = pr;
                else if((pk = p.key) == k || (k ! =null && k.equals(pk)))// If the current node is equal to the given hash value and the key is the same, the current node is returned
                    return p;
                else if (pl == null)// If the left node is null, the right node is entered
                    p = pr;
                else if (pr == null)// If the right node is null, the left node is entered
                    p = pl;
                else if((kc ! =null|| (kc = comparableClassFor(k)) ! =null) && (dir = compareComparables(kc, k, pk)) ! =0)// If you sort by the comparator instead of the hash value, then the comparator returns the value to decide to enter the left and right nodes
                    p = (dir < 0)? pl : pr;else if((q = pr.find(h, k, kc)) ! =null)// If the keyword is found in the right node, return the current node directly
                    return q;
                else// Enter the left node
                    p = pl;
            } while(p ! =null);
            return null;
        }
Copy the code

2.4 the remove ()

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null.false.true)) = =null ?
            null : e.value;
    }
Copy the code
    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;
        // Check whether table exists, data element >0, index element exists
        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;
            // check that p is consistent with the data passed in
            if(p.hash == hash && ((k = p.key) == key || (key ! =null && key.equals(k))))
                // get p data
                node = p;
            else if((e = p.next) ! =null) {// The data of the next node of p is inconsistent with that of P
                // Check whether the data structure of p is a red-black tree
                if (p instanceof TreeNode)
                    // Red-black tree, call find to find node
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    / / list
                    do {
                        // loop to find the same data as e
                        if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while((e = e.next) ! =null); }}// Determine the obtained data
            if(node ! =null&& (! matchValue || (v = node.value) == value || (value ! =null && value.equals(v)))) {
                // Delete data according to red-black tree
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)//node=p, then pointer to the next node after p, to delete node
                    tab[index] = node.next;
                else// node= p.ext, so node is killed
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                returnnode; }}return null;
    }
Copy the code

This method is similar to the put/get method above, which is to obtain the hash value, and then find the node according to the hash value and Key, delete, explore a lot of fun.

2.5 the clear ()

    public void clear(a) {
        Node<K,V>[] tab;
        modCount++;
        if((tab = table) ! =null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null; }}Copy the code

This is a straightforward way to iterate over the table and set the value of the table to NULL.

In fact, there are only a few common methods of HashMap, you know?

Three, question and answer small knowledge

3.1 Why Do I Always Use String and Integer for keys

In the syntax of a HashMap, any object can be a Key. For example, Integer, Long, String, and Object. But in practice, String is most commonly used as the Key.

  • They are all final modified classes that are immutable, ensuring that keys cannot be modified and that hash values cannot be obtained differently.
  • Internally, they override equals(), hashCode(), etc., to follow the internal specification of HashMap.
  • They have their own separate properties, and they are placed in the constant area (to quickly determine whether they are equal or not).

3.2 When can HashMap be expanded?

There is a paragraph in the put method above

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {...if(++size > threshold) resize(); . }Copy the code

If the map elements are greater than threshold = Capacity (current map size) x Load factor(default: 0.75).

Call resize() to expand capacity


    static final int MAXIMUM_CAPACITY = 1 << 30;
    final Node<K,V>[] resize() {
        ...
        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}...Copy the code

>>: in binary form, all digits are moved to the left by the corresponding digit, the higher digit is moved out (discarded), and the lower digit is filled with zero. Move oldCap 1 bit to the left.

Limitation: cannot exceed a maximum of 2 to the 30th power

3.3 Why is expansion 2 to the NTH power?

For efficient access, a HashMap should have as few collisions as possible. The idea is to distribute the data as evenly as possible, with each list roughly the same length.

For example, hashmap.get (“name”).hashcode ()=773564.

773564 to binary: 10111100110110111100

Modulus operation

Modulo operation: Obviously, when the hashmap size is not 2 to the NTH power, there are more hash collisions.

tab[(n - 1) & hash]) 
Copy the code

In hash algorithms, in order to distribute elements more evenly, modulus calculation is often used. In hashMap, (n-1) & hash is used instead of hash%n for modulus calculation. The reason is that in computers, ampersand is much more efficient than %; Note that (n-1) & hash is equivalent to hash%n only if the capacity is 2 to the NTH power, which is why the initial capacity of hashMap initialization must be 2 to the NTH power.

3.4 Why do 16 bits higher or 16 bits lower before modular operation?

Due to hashCode and (length-1) operations, length is mostly less than 2 to the 16th power. So the lower 16 bits of HashCode (or even lower) are always involved. If the higher 16 bits were involved, it would make the resulting index more hashed.

3.5 What methods should be implemented if the key in a HashMap is the defined entity class type?

        // The equals(), hashCode() methods are not overridden
        System.out.println("Unoverride equals(), hashCode(), etc.");
        HashMap<User,String> userMap = new HashMap<>();
        User userQin = new User(Ying zheng "".20);
        userMap.put(userQin,The First Emperor of Qin);
        // When you define MapUser and put the data does not change, map.get can get the data
        System.out.println(userMap.get(userQin));/ / the qin shi huang
        System.out.println("Age20:"+userQin.hashCode());//Age20:1531333864

        // When mapUser. age changes, map. get cannot get data
        userQin.age=25;
        System.out.println("Age25:"+userQin.hashCode());//Age25:1531333864
        System.out.println(userMap.get(userQin));/ / the qin shi huang

        userMap.put(new User("Liu".20),Emperor Gaozu of the Han Dynasty);
        System.out.println(userMap.get(new User("Liu".20)));//null

        // Override equals(), hashCode() methods
        System.out.println("Override equals(), hashCode()");
        HashMap<HMUser,String> hmUserMap = new HashMap<>();
        HMUser hmUserLi = new HMUser("Li Shimin".20);
        hmUserMap.put(hmUserLi,"Tang Taizong");
        // When you define MapUser and put the data does not change, map.get can get the data
        System.out.println(hmUserMap.get(hmUserLi));/ / emperor taizong
        System.out.println("Age20:"+hmUserLi.hashCode());//Age20:807921772

        // When mapUser. age changes, map. get cannot get data
        hmUserLi.age=25;
        System.out.println("Age25:"+hmUserLi.hashCode());//Age25:807921777
        System.out.println(hmUserMap.get(hmUserLi));//null

        hmUserMap.put(new HMUser("Zhu Yuanzhang".20),"Ming Tai Zu");
        System.out.println(hmUserMap.get(new HMUser("Zhu Yuanzhang".20)));/ / Ming emperor
Copy the code
  • Without overriding equals(), hashCode() :

    • The hashCode value remains the same after the same object property is changed, which can be obtained from map.get.
    • Different objects have the same attributes and cannot get values from map. get.
  • When overriding equals(), hashCode() :

    • If the same object property changes, the hashCode will also change, and the value cannot be obtained from map. get.

    • Different objects have the same properties and can get values from map. get.

3.6 Why is HashMap not thread-safe

  • If thread A and thread B insert at the same time and calculate the same hash value corresponding to the same array position, A writes to A node first and B writes to the same node, then B’s write operation will overwrite A’s write operation and cause A’s write data to be lost.

  • When HashMap is expanded: a new array of new capacity is generated, and all the key pairs of the original array are re-evaluated and written to the new array, and then the new array is pointed to. When multiple threads enter at the same time and detect that the total number exceeds the threshold, the resize operation will be called at the same time to generate new arrays respectively. Finally, only the new array generated by the last thread will be assigned to the bottom layer of the map, and all the other threads will be lost.

ConcrrentHashMap, ConcrrentHashMap, ConcrrentHashMap,

ConcrrentHashMap:

  • The bottom layer of the segmented array + linked list implementation, thread safety.

  • By dividing the entire Map into N segments, this provides the same thread-safety, but an n-fold increase in efficiency and a 16-fold increase by default (read operations are not locked, and the latest values are guaranteed because the Node value variable is volatile).

  • ConcurrentHashMap allows multiple modification operations to occur concurrently, and the key is the use of lock separation.

  • Some methods that span segments, such as size() and containsValue(), may need to lock the entire table instead of just one segment, which requires locking all segments sequentially and releasing all segments sequentially when done.

  • Capacity expansion: Capacity expansion within a segment (If the number of elements in a segment exceeds 75% of the length of the corresponding Entry array, capacity expansion will not be performed on the entire Map). Check whether the Map needs to be expanded before insertion to avoid invalid capacity expansion.

Lock segmentation technology: firstly, the data is divided into sections for storage, and then each section of data is equipped with a lock. When a thread uses the lock to access one section of data, the data of other sections can also be accessed by other threads.

ConcurrentHashMap Divides the hash table into 16 buckets by default. Common operations such as GET, PUT, and remove lock only the buckets that are currently used. As a result, a single thread can now be entered by 16 writers at the same time, and the concurrency improvement is noticeable.