Deep parsing HashMap set underlying principle

Pre knowledge #

==The difference between and equals #

For basic type variables, you can only use = =, because basic type variables have no methods. Use = = value comparison

For variables of reference type, whether the addresses of the two reference objects compared by = = are equal. All classes inherit from the object class, and the object class uses the equals method to compare whether the addresses of the objects are equal. If the class does not override the equals method, using = = has the same effect as using the equals method

Why rewrite equals and HashCode #

HashCode method: the bottom layer is written in C language and converted to integer type according to the object address

If the hashcodes of two objects are equal, the contents of the objects are not necessarily equal; hash collision problem

If you use the equals method to compare two objects with the same content value, the hashcode value is also equal

Because equals by default, the Object class uses = = to compare objects. The comparison is whether the memory addresses are equal. As long as the data type is not a basic type, the comparison will never be equal.

The set set stores non duplicate objects, and the bottom layer is hashmap, which is judged according to equals and hashcode

Time complexity #

The time complexity is O(n) query from the beginning to the end and query multiple times

The time complexity is O(1) query once, such as according to the array subscript

Queries with time complexity of O(logn) square, such as red black trees,

Efficiency: O (1) > O (logn) > O (n)

(move right without symbol) > > > #

Unsigned right shift means that after the right shift, no matter whether the number is positive or negative, the left is supplemented by 0 after the right shift

The main difference between the unsigned right shift operator and the right shift operator is the calculation of negative numbers, because the unsigned right shift is the high complement of 0, and how many bits are shifted complement how many zeros

15> > > 2 = 0000 1111 shift right two bits = 0000 0011 = 3

^XOR operation #

The same is 0, the difference is 1

2^3= 0010^0011=0001=1

&(and operation) #

00 0 11 1 01 0

2&3=0010&0011=0010=2

Displacement operation: 1 < < 2 = 4, why does 1 shift left two bits equal to 4 #

1 here is decimal, and computer communication is binary, so 1 should be expressed in binary first.

Each symbol (English, number or symbol, etc.) will occupy 1Bytes of records, and each Chinese symbol will occupy 2Byte

A 1Bytes occupies 8 bits, that is, 8 binary bits

8-bit binary number: 28 different states 0000 ~ 1111 1111 = 0 ~ 255 = 28 = 256

The binary representation of 1 is 0000 0001, and then the displacement operation is performed.

The displacement operation shifts to the left, and 0 is added to the empty space behind. The more to the left, the larger. The 0000 0001 is shifted to the left by 2 bits, which becomes 0000 0100. The binary 0000 0100 is converted to decimal, so it is 4. It can also be said that every bit shifted to the left is multiplied by 2

8>>2?

0000 1000 shifts 2 bits to the right 0000 0010, which is converted to hexadecimal equal to 2

10>>2?

0000 1010 shifts 2 bits to the right 0000 0010, which is converted to hexadecimal equal to 2

1<<30?

0000 0001 shifts 30 bits to the left 01000000 000000 00000000 00000000 00000000, which is converted to hexadecimal, equal to 1073741824, that is, 230

HashMap collection features and source code analysis (JDK1.8) #

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

HashMap inherits the AbstractMap class and implements Cloneable cloning interface, Serializable serialization interface and Map interface

Features: array + linked list + red black tree

Five important points of HashMap #

1. Set initialization #

HashMap member variable #

//Default initialization hashmap capacity
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //The fourth power of 2 16
//hashmap maximum capacity 1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;//The 30th power of 2
//Capacity expansion factor 16 * 0.75 = 12. Capacity expansion will be carried out when it reaches 12
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//When the number of elements stored in the linked list is > 8, it will be automatically converted to a red black tree
static final int TREEIFY_THRESHOLD = 8;
//When deleting elements, if the number of elements stored in a red black tree is less than 6, it will be automatically converted to a linked list
static final int UNTREEIFY_THRESHOLD = 6;
//Array capacity > 64 & linked list length > 8 is converted to red black tree
static final int MIN_TREEIFY_CAPACITY = 64;

//Threshold value, used to judge whether to expand capacity, threshold = capacity * expansion factor = 16 * 0.75 = 12
int threshold;
//Actual size of expansion factor
final float loadFactor;
//The number of elements in the HashMap, transient, indicates that it cannot be serialized
transient int size;
//The number of collection modifications prevents multiple threads from tampering with data
transient int modCount;
//One way linked list of arrays storing elements
transient Node<K,V>[] table;

HashMap internal data structure #

Linked list

//The one-way linked list implements the Entry interface, and the above array constitutes the structure of array plus linked list
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    //Construct a node
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    //Basic method
    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;
    }
   //Compare whether two nodes are equal
    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;
    }
}

Red black tree

//Overview of red black tree structure
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;//Left subtree
    TreeNode<K,V> right;//Right subtree
    TreeNode<K,V> prev;    //
    boolean red;//Is it red
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

  //Returns the current node
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }
}

HashMap construction method #

Specify initial capacity

Construct an empty HashMap with default initial capacity (16) and default load factor (0.75)

static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; //Expansion factor 0.75
}

Specify fill ratio

Construct an empty HashMap with the specified initial capacity and default expansion factor (0.75)

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);//The initial capacity expansion factor is 0.75 by default
}
public HashMap(int initialCapacity, float loadFactor) {
    //Illegal capacity judgment
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //Is it greater than the maximum capacity? It is not allowed to exceed the maximum capacity
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
     //Illegal judgment of expansion factor
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;//Load factor
    this.threshold = tableSizeFor(initialCapacity);
}

//The power of the smallest 2 greater than cap is obtained, for example, the power of the smallest 2 of 10, 10 = 16
static final int tableSizeFor(int cap) {
        int n = cap - 1;//n=9   0000 1001
        n |= n >>> 1;//|=Represents that the XOR operation first shifts 1 bit to the right = 0000 01000000 1001 and 0000 0100. The XOR operation obtains 0000 1101
        n |= n >>> 2;//... and so on
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
    //N < 0 returns 1, otherwise n is greater than maximum_ If the capacity is less than the maximum value, n + 1 will be returned. In the case of all 1, + 1 must become 1 followed by a pile of 0. This determines the final value. When the size of the HashMap reaches the threshold, it will expand
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

Map used to initialize

Construct a new HashMap using the same mapping as the specified Map

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;//Default load factor 0.75
    putMapEntries(m, false);
}
//put collection elements into HashMap
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    //Get element size
    int s = m.size();
    if (s > 0) {
        //If the array of storage elements is empty, it indicates that this is a newly constructed HashMap, then you need to specify the maximum capacity for it
        if (table == null) { 
            //The maximum capacity is calculated according to the threshold and Map size, and rounded up to get an integer
            float ft = ((float)s / loadFactor) + 1.0F;
            //Judge whether the capacity exceeds the maximum capacity, and assign a value directly if it does not exceed the maximum capacity
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            //If the capacity is greater than the threshold 
            if (t > threshold)
                //Recalculate threshold
                threshold = tableSizeFor(t);
        }
        //The array has been initialized
        else if (s > threshold)
            resize(); //Capacity expansion first
        // Cyclic put
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            //resize may be triggered
            putVal(hash(key), key, value, false, evict);
        }
    }
}

2. Data addressing Get #

Get elements according to key

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

Detailed method

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; 
        Node<K,V> first, e; 
        int n; K k;
     //If table is not equal to null, the array is not equal to null, and the first = assignment calculates the array subscript position where the hash value of the current node is located
        if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {
            //If the current node is compared with the first node 
            if (first.hash == hash &&((k = first.key) == key || (key != null && key.equals(k))))
                //Return to the first node
                return first;
            //If the next node of the first node is not null
            if ((e = first.next) != null) {
                //Judge whether it is a red black tree
                if (first instanceof TreeNode)
                    //Process getTreeNode() method to search for key
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //It's a linked list
                do {
                  //Traverse the comparison until the node is found or the node is null to exit the loop
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

3. Data storage Put #

Add elements to HashMap

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

Detailed method

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; 
        Node<K,V> p; 
        //n indicates the length of the table array, and i indicates which array subscript the key is stored in
        int n, i;
      //Judge whether the global table=tab is empty or whether the tab length is 0, and expand the capacity of the table
        if ((tab = table) == null || (n = tab.length) == 0)
            //Expansion n=16
            n = (tab = resize()).length;
       //I = (n - 1) & hash calculate the index value corresponding to the key tab [i] whether the key exists in the array 
        if ((p = tab[i = (n - 1) & hash]) == null)
           //If the index value of the key does not conflict 
            tab[i] = newNode(hash, key, value, null);
     //The index of key conflicts
        else {
            Node<K,V> e; K k;
            //If the hash and equals comparisons are the same, overwrite them directly
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //If it is a red black tree
            else if (p instanceof TreeNode)
                //Append to red black tree
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //It is currently a linked list
            else {
                //Loop through linked list
                for (int binCount = 0; ; ++binCount) {
                    //If the linked list is empty, it is directly appended after next
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //If the linked list length binCount is greater than 8, the array capacity is greater than 64
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            //Convert the linked list into a red black tree
                            treeifyBin(tab, hash);
                        break;
                    }
                    //Query whether the key exists in the linked list. If so, modify the value directly
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //Really assign a value to e and overwrite the new value with oldvalue
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
       //Only by adding a new one will the modCount + + modification not be affected. The fastclass mechanism will prevent the collection modification class during traversal
        ++modCount;
       //If size > 12, the capacity will be expanded in advance
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

4. Node deletion #

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
    }

Detailed method

//Hash: hash value of key key: key value of key value pair to delete key value: value of key value pair to delete
//If matchValue is true, it will be deleted only when the value equals(value) of the key value pair corresponding to the key is true; Otherwise, you don't care about the value of value
//movable whether to move the node after deletion. If false, the node will not be moved
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;
    //The node array tab is not empty, the array length n is greater than 0, and the node object p located according to the hash
        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;
            //If the key and key of the current node are equal, the current node is the node to be deleted and assigned to node
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //Gets the next node of the current node
            else if ((e = p.next) != null) {
                //If it is a red black tree, call the getTreeNode method to find the nodes that meet the conditions from the tree structure
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                //If it's a linked list
                else {
                    //Node by node comparison from beginning to end
                    do {
                        //Whether the key of node e is equal to the key. Node e is the node to be deleted and assigned to the node variable
                        if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;//p points to e, so that p will always store the parent node of E in the next cycle
                    } while ((e = e.next) != null);
                }
            }
            //Node is not empty. The deleted node is found. If the value value does not need to be compared or the value value needs to be compared, but the value value is also equal
            if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {
                //If it is a node on a red black tree
                if (node instanceof TreeNode)
                    //delete
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //If it is a linked list, the node node is the first node
                else if (node == p)
                    //Delete assigns the next value of the current node to the current table index
                    tab[index] = node.next;
                //Not the first node, p is the parent node of node
                else
                    // Deleting the next node of the parent node is the next node of the node 
                    p.next = node.next;
                ++modCount;//The number of modifications of HashMap increases
                --size;//Number of elements in HashMap
                afterNodeRemoval(node);//The reserved override method has no effect
                return node;//Return deletion result
            }
        }
        return null;
    }

5. Capacity expansion principle #

Resizing mechanism in JDK1.7 HashMap () #

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //If the old capacity has reached the maximum, set the threshold to the maximum value, the same as 1.8
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //Create a new hash table
        Entry[] newTable = new Entry[newCapacity];
        //Transfer the data from the old table to the new hash table
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //Update threshold
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        //Traverse all linked lists in the original array
        for (Entry<K,V> e : table) {
            //Judge whether the linked list storage corresponding to each subscript is empty
            while(null != e) {
                //If you get e.next in both multithreaded environments, there may be an endless loop problem
                Entry<K,V> next = e.next;
                //Do you need to recalculate the hash value
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                //Get the index in the new table
                int i = indexFor(e.hash, newCapacity);
                //Add the new node as the head node to the bucket
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

Analysis process of dead loop caused by capacity expansion in multithreaded environment

     for (Entry<K,V> e : table) {
          while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
     }

Because the header insertion method is used, the order of the whole linked list will be reversed. Traversing the table in a multithreaded environment is easy to lead to an endless loop because the same e object is manipulated

In the case of simultaneous capacity expansion of multiple threads, thread 1 obtains CPU resources first, while thread 2 is suspended. At this time, the data they get is e=a; next=c; As soon as the thread takes the lead in executing, it calculates the key and puts it into the newTable

At this time, thread 2 is awakened again. Because threads are not shared, the newTable is also empty and needs to be re assigned, but the e variable can be shared.

1. The first cycle of thread 2 or the data obtained before e=a; next=c, and the newTable is still empty

// e=a  e.next=c       
Entry<K,V> next = e.next;
//1
int i = indexFor(e.hash, newCapacity);
//e.next=null
e.next = newTable[i];
//The first element at the position of array 1 is assigned a
newTable[i] = e;
//e=c
e = next;

2. Thread second loop

In the second cycle, the data of e is obtained, because the data of e has been modified by thread 1 and changed to D - > C - >

// e=c   e.next=a     
Entry<K,V> next = e.next;
//1
int i = indexFor(e.hash, newCapacity);
//e.next=null
e.next = newTable[i];
//The position of array 1 is assigned c
newTable[i] = e;
//e=a
e = next;

Because e has been modified by thread 1 to become D - > C - > A, c.next is equal to the value in thread 1, c.next=a

3. Thread second third loop

//e=a;  e.next=a.next=null     
Entry<K,V> next = e.next;
//1
int i = indexFor(e.hash, newCapacity);
//At this time, newtable [i] = a - > C e.next = A.Next A.Next = C - > a dead cycle occurs  
e.next = newTable[i];
//The position of array 1 is assigned a
newTable[i] = e;
//e=null
e = next;

At this point, the ring reference is entered, causing the CPU utilization to soar in the infinite loop

terms of settlement

There will be no problem if there is no single thread. ConCurrentHashMap is used in multi thread

Resizing mechanism in JDK1.8 HashMap () #

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
       //If the original table=null, it is the initialization of HashMap, and an empty table can be generated and returned
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //Threshold of hashmap's next expansion
        int oldThr = threshold;
        //Record the new capacity and the new next expansion size
        int newCap, newThr = 0;
        //Greater than 0 indicates that the array of previous HashMap is not empty  
        if (oldCap > 0) {
            // Then check the array. If it is greater than the 30th power of the maximum capacity 2, it will be returned directly
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //If not, expand the capacity twice as much as before. Based on the right shift, the newCap is twice the length of the oldCap
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //Quantity of next advance expansion
                newThr = oldThr << 1;
        }
        //A threshold greater than 0 indicates that the collection already exists and is assigned to newCap
        else if (oldThr > 0) 
            newCap = oldThr;
        //If it is equal to 0, it means that newcap = 0.75 has just been initialized, newthr = threshold of new capacity expansion = 0.75 * default capacity size
        else {              
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
       //If the next advance expansion quantity = = 0
        if (newThr == 0) {
            //New threshold = new capacity * load factor
            float ft = (float)newCap * loadFactor;
            //The new array is less than the maximum capacity and the threshold is less than the maximum capacity 
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //Threshold of next capacity expansion
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
          //New capacity expansion in HashMap
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
      //If the original table is not empty, move the data in the original table to the new table
        if (oldTab != null) {
            //Traverse the original list
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //Judge whether there is a linked list in each array. If so, save it with a linked list e
                if ((e = oldTab[j]) != null) {
                    //Then assign the original linked list to null to avoid dead loop
                    oldTab[j] = null;
                    //The next node is empty, indicating that it contains only one element 
                    if (e.next == null)
                        //Calculate the position of e in the new table and put it in it
                        newTab[e.hash & (newCap - 1)] = e;
                    //Judge whether the current node is a red black tree
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //Linked list
                    else { 
                        //hashmap expansion will split the original linked list into two linked lists
                        //Head and tail of low linked list
                        Node<K,V> loHead = null, loTail = null;
                        //Head and tail of high-order linked list
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        //Loop splits the linked list into two linked lists
                        do {
                            next = e.next;
                            //oldCap=16 when the hash subscript remains unchanged 
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                             //Hash subscript change
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //Both methods put the linked list in a new position
                        //Put in the original position of the new table
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //Place in new table j+oldCap position
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

HashMap 1.8 unpacks the linked list into two linked lists through operation and stores them in a new table

HashMap interview questions #

When will HashMap be converted to red black tree #

When the array capacity is greater than 64 and the linked list length is greater than 8

Why should HashMap introduce red and black trees? What are the problems to be solved? #

The time complexity of linked list query is O(n), and the query efficiency is too low. The query efficiency of referencing red black tree can be changed to O(logN)

Why must the length of HashMap be a power of 2? #

In this way, the length must be even. When calculating the index subscript (n-1) & hash, so (n-1) will be odd, and the odd & hash value will reduce the conflict. The integer power of n to 2 is to reduce the probability of collision between different hash values, so that the elements can be evenly hashed in the hash table

How to avoid HashMap memory overflow #

Because hashcode and equals and methods are not rewritten, the bottom layer uses = = to compare memory addresses by default, which will lead to multiple objects in new. After rewriting, each comparison will be the same object and will be overwritten.

Time complexity of HashMap query based on key #

If the key has no conflict and the time complexity is O(1), it can be found at one time

If the key conflicts, the linked list is stored as O(n), and the red black tree is stored as O(logn)

Where is the HashMapKey stored when it is null #

Position 0

int index = k == null ? 0 : k.hashCode() % objects.length;

Is the HashMap bottom layer a single linked list or a double linked list #

Unidirectional linked list

Is the underlying HashMap stored in order #

One way linked list stores unordered hash, which will traverse all linked lists and red black trees, which is very inefficient

How do LinkedHashMap and TreeMap bottom layers achieve ordered #

Principle: associating the linked list in each index is less efficient than HashMap

Underlying implementation principle of cache elimination algorithm LinkedHashMap

How to clean up Redis when its cache is full?

LUR algorithm: clean up recently used key s

Scheme 1: record the usage times of each key, then sort and delete it, which is very inefficient

Scheme 2: when accessing the key based on the LinkedHashMap ordered set, the key will be saved to the last position in the linked list

Insertion order: the first added is in front, and the last added is in the back. The modification operation does not affect the order

After the get/put operation, the corresponding key value pair will move to the end of the linked list, so the last is the most recently accessed, and the first is the longest not accessed. This is the access order.

The parameter accessOrder is used to specify whether to access in order. If it is true, it is the access order.

Why does HashMap not use modulo operations #

k. Hashcode()% entries.length modulus will lead to very high probability of key conflict

It will become linked list O(n) or red black tree O(logn). It is necessary to reduce the Hash conflict probability and place it evenly at each subscript of the array

Find the subscript i = (n-1) & hash, why (n-1) becomes an odd number #

By default, capacity expansion is an even number to the nth power of 2. If it is an even & (and operation) hash, the index conflict probability is very high, which will lead to uneven data distribution

So it needs to be odd

How can HashMap reduce Hash conflicts #

The hash function calculates I = (n-1) & hash. The probability of hash value conflict can be reduced by odd residual hash values

Why is the loading factor 0.75 instead of 1 #

If the loading factor is larger (1), the space utilization is relatively high, and all 16 locations are filled, so the index conflict probability is relatively high

If the loading factor is smaller (0.1) and the capacity is expanded when it reaches 0.1, the smaller the space utilization is and the more locations can be stored. In this way, the smaller the collision probability is

Balance point in space and time: 0.75

Statistical probability: Poisson distribution is a common discrete probability distribution in statistics and probability

How does Hashap store 1W pieces of data? It is the most efficient #

hashmap capacity = (number of elements to be stored / expansion factor) + 1 = (10000 / 0.75) + 1 = 13334

The purpose is to reduce the number of underlying capacity expansion. If the initial capacity is not set, hashmap needs to be expanded seven times, which seriously affects the performance

Differences between Hashmap1.7 and Hashmap1.8 #

Hashmap1.7 implements header insertion based on array + linked list. The writing method is simple, but there is a multi-threaded dead loop problem

Hashmap1.8 implements tail interpolation based on array + linked list + red black tree to solve the problem of multi-threaded dead loop

It can reduce the conflict probability of the index corresponding to the key and improve the query rate

The original linked list use and operation hash & the length of the original table, split into two linked lists and put them into a new array, which can shorten the length of the linked list and improve the query efficiency

Keywords: HashMap

Added by dhodge on Fri, 29 Oct 2021 07:23:25 +0300