Core source code analysis of LinkedHashMap

Article directory


LinkedHashMap itself inherits from HashMap, so it has all the features of HashMap. On this basis, it also provides two features:

  1. Access in the order of insertion;
  2. It realizes the function of the least access and the first deletion. Its purpose is to automatically delete the key that has not been accessed for a long time.

1 access in insertion order

1.1 LinkedHashMap linked list structure

// Inherit Node and add before and after attributes for each element of the array
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

// Chain header
transient LinkedHashMap.Entry<K,V> head;

// List end
transient LinkedHashMap.Entry<K,V> tail;

// Control the fields of two access modes, false by default
// true according to the access order, the frequently accessed key s will be put at the end of the team
// false provides access in insertion order
final boolean accessOrder;

The data structure of LinkedHashMap is very similar to replacing each element of LinkedList with a Node of HashMap. It is like a combination of the two. It is precisely because these structures are added that the elements of Map can be connected in series to form a linked list. The linked list can ensure the order and maintain the order in which the elements are inserted.

1.2 how to add in sequence

When LinkedHashMap initializes, the default accessOrder is false, that is, it will provide access in the insertion order. The insertion method uses the put method of the parent class HashMap, but overrides the newNode/newTreeNode and afterNodeAccess methods invoked in the put method execution.

The newNode/newTreeNode method controls the addition of new nodes to the end of the linked list, so that every time new nodes are added to the end, the insertion order can be guaranteed. Let's take the newNode source code as an example:

// Add a new node to the end of the list
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    // New node
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // Append to the end of the list
    linkNodeLast(p);
    return p;
}
// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    // New node equals bit node
    tail = p;
    // Last is empty, indicating that the link list is empty and the first and last nodes are equal
    if (last == null)
        head = p;
    // The linked list has data. Directly establish the relationship between the new node and the last tail node
    else {
        p.before = last;
        last.after = p;
    }
}

LinkedHashMap adds before and after attributes to each node by adding a new head node and tail node. Each time it adds a new node, it appends the node to the tail node and other means. When it adds a new node, it already maintains the chain list structure according to the insertion order.

1.3 access in sequence

LinkedHashMap only provides one-way access, i.e. access from the beginning to the end according to the order of insertion. It cannot be accessed in two directions like LinkedList.

We mainly access through iterators. When the iterators are initialized, they are accessed from the beginning of the node by default. In the process of iteration, we can access the after node of the current node continuously.

Map provides iterative methods for key, value and entity (node). If we need to iterate entity, we can use LinkedHashMap.entrySet().iterator() to directly return LinkedHashIterator. LinkedHashIterator is iterator. We call nextNode method of iterator to get the next node. The source code of iterator is as follows:

// When initializing, it is accessed from the beginning of the node by default
LinkedHashIterator() {
    // Head node as the first visited node
    next = head;
    expectedModCount = modCount;
    current = null;
}

final LinkedHashMap.Entry<K,V> nextNode() {
    LinkedHashMap.Entry<K,V> e = next;
    if (modCount != expectedModCount)// check
        throw new ConcurrentModificationException();
    if (e == null)
        throw new NoSuchElementException();
    current = e;
    next = e.after; // Through the after structure of the linked list, find the node of the next iteration
    return e;
}

When adding a new node, we have maintained the insertion order between elements, so iterative access is very simple, only the next node of the current node needs to be accessed continuously.

2 access minimum delete policy

This strategy is also called LRU (Least recently used). It means that the frequently accessed elements will be appended to the end of the queue, so that the infrequently accessed data will naturally be close to the queue head. Then we can set the deletion strategy, for example, when the number of Map elements is greater than, we can delete the head node.

2.1 elements transferred to the end of the team

When get is called, the element is moved to the end of the queue:

public V get(Object key) {
    Node<K,V> e;
    // Call the HashMap get method
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // If LRU policy is set
    if (accessOrder)
    // This method moves the current key to the end of the team
        afterNodeAccess(e);
    return e.value;
}
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

From the above source code, we can see that the current access node is moved to the end of the queue through the afterNodeAccess method, which is not only the get method, but also the getOrDefault, compute, computeIfAbsent, computeIfPresent, merge Method, it will do the same. By constantly moving the frequently visited nodes to the end of the team, the nodes near the team head will naturally be the rarely accessed elements.

2.2 deletion strategy

LinkedHashMap itself is not implemented by the put method. It calls the put method of HashMap, but LinkedHashMap implements the call of afterNodeInsertion method in the put method. This method implements deletion. Let's look at the source code:

// Delete rarely accessed elements, called by the put method of HashMap
void afterNodeInsertion(boolean evict) { 
    // Get element header node
    LinkedHashMap.Entry<K,V> first;
    // Removealdesetentry to control the deletion policy. If the queue is not empty and the deletion policy allows deletion, delete the header node
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // Removinode deleting a header node
        removeNode(hash(key), key, null, false, true);
    }
}

3 Summary

LinkedHashMap provides two functions: to access and delete the least access elements in accordance with the insertion order strategy, which is implemented simply through the structure of the linked list, and is designed very cleverly.

392 original articles published, 58 praised, 220000 visitors+
His message board follow

Posted by testtesttesttest on Sun, 09 Feb 2020 23:40:07 -0800