Java: HashMap source code analysis

HashMap, as one of our most commonly used data structure collections, has its characteristics. We can look at its characteristics.

First, let's look at the data structure model of HashMap. HashMap is a collection of arrays and linked lists. The same hash code is a set, which is stored in the form of an array. The same data for each hash code is stored in the form of a linked list.
Write picture description here

In the jdk1.8 environment, we look at the main methods of HashMap: constructor, put() and get().
Let’s take a look at the constructor [default constructor]:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

Let's take a look at loadFactor is the meaning of capacity factor. The loadFactor capacity factor is 0.75. Actual capacity factor = actual storage number/capacity. If it exceeds 0.75, the capacity will be expanded.

put() method:

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

We followed up the put() method, but saw five parameters passed in. The first is hash(key), and then...
Let’s look at the implementation of the hash() method:

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

Let's see, the content returned by hash(key) is the processed hash value, and we can reduce hash conflicts through this operation. Hash conflict, you can refer to: click for reference . After this calculation, the number of hash conflicts is reduced a lot.

We follow up the putVal(hash(key), key, value, false, true) method:

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

The putVal() method is a bit long. But let's analyze it slowly.
First of all, we must know that HashMap has an attribute that is transient Node<K,V>[] table;the table in the above figure.
Execute tab=table, and give the contents of table to tab. Then if the table content is null, we call resize() which is equivalent to reallocating the size of the table.
Execute p=tab[i = (n-1) & hash], that is, take the (n-1)&hashth in the tab content. If there is no null currently, that is to say, the header of the (n-1)&hashth linked list is null. We will create a new node for tab[i].
If it is not null, it means that the (n-1)&hashth has a value, and we will traverse this linked list. If a suitable location is found, the node is added to the linked list.

The get() method is the same.
Let's look at the implementation of the get() method:

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

Mainly the implementation of getNode()

 final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

In fact, the main idea is the same. First find the subscript of the table element that is the same as hash(key), and then we go to traverse the subscript.

After understanding the source code structure of HashMap, we can make the following summary:
1. After the content of HashMap is changed, the order cannot be guaranteed to be the same.
2. The storage of key-value pairs in HashMap has nothing to do with value, only hash(key).
3. HashMap does not implement thread synchronization.
4. HashMap allows key and value values ​​to be null

Guess you like

Origin blog.csdn.net/new_Aiden/article/details/51001587