Java source code Series 3 - LinkedHashMap

What is LinkedHashMap?

LinkedHashMapIt is HashMapthe orderly implementation. LinkedHashMapWith a doubly linked list to maintain order, iteration, it also uses the iterator's own implementation.

public static void main(String[] args) {
    HashMap<String, Integer> h = new HashMap<>(33);
    h.put("one", 1);
    h.put("two", 2);
    h.put("three", 3);
    h.put("four", 4);
    for (String key : h.keySet()) {
        System.out.println("key:" + key + "value:" + h.get(key));
    }

    LinkedHashMap<String, Integer> lh = new LinkedHashMap<>(33);
    lh.put("one", 1);
    lh.put("two", 2);
    lh.put("three", 3);
    lh.put("four", 4);
    for (String key : lh.keySet()) {
        System.out.println("key:" + key + "value:" + lh.get(key));
    }
}
复制代码

Export

key:twovalue:2
key:threevalue:3
key:fourvalue:4
key:onevalue:1

key:onevalue:1
key:twovalue:2
key:threevalue:3
key:fourvalue:4
复制代码

Underlying array structure

HashMap is a bottom arrays, linked lists, consisting of red-black tree. An array of nodes for storing, linked lists stored hash collision occurs when, after more than a certain length of the list will be optimized for red-black tree.

In addition to inherited from underlying LinkedHashMap HashMap array, linked list, red-black tree, but also more than a doubly linked list links all the nodes (red and green arrow in the figure) for sequentially storing the respective nodes.

Entry of inheritance

LinkedHashMap.EntryInherited HashMap.Node, maintained before and after more than two pointers, two attribute points after the previous Entry and Entry of an Entry, the piece is doubly linked list is used to store the order.

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);
    }
}
复制代码

However, no really LinkedHashMap in the HashMap TreeNode override code, regardless of the order that each node is stored in the red-black tree.

We can from the HashMap.TreeNodeinheritance of clues to find out:

Yo roar, the Xiaojia Zi's emotional chaos, a subclass inherits the parent class inside the class, inner class inherits the parent class and subclass of class interior, staged a chicken-egg drama .

// 继承了 LinkedHashMap.Entry
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
}
复制代码

Why HashMap.TreeNodeshould inherit LinkedHashMap.Entry, inherited before and after the pointer has not been used in a HashMap, why not directly inherited HashMap.Node?

This inheritance is not really designed for the HashMap, HashMap does in no use. But in LinkedHashMap, you can directly use the inherited HashMap.TreeNode, because this class through inheritance TreeNode already have before and after pointer.

That is why, LinkedHashMapthere is an inherited HashMap.Nodeinner class, but did not inherit HashMap.TreeNodethe inner class.

Creation of the list

Creation of the list is the first element into when it started, at the beginning of the list head (head) and tail (tail) are null.

LinkedHashMapThe method does not put override the parent class, the insertion process elements substantially the same, only HashMapthe insertion of a Nodetype of node, LinkedHashMapinsert the Entrytypes of nodes, and updates the list.

So LinkedHashMapis how insert nodes, and update the list of it?

// HashMap 中实现
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// HashMap 中实现
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);
    // 哈希碰撞了,本节不介绍,可以看上一篇讲 HashMap 的文章
    else {
        // ... 省略部分代码
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

// HashMap 中实现的 newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

// LinkedHashMap 中覆写的 newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// LinkedHashMap 中实现
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // 如果链表为空,头部和尾部都赋值为p
    if (last == null)
        head = p;
    // 把新插入的节点放在链表尾部
    else {
        p.before = last;
        last.after = p;
    }
}
复制代码

Can clearly be seen from the code, the assignment list in the index calculation LinkedHashMap, barrels, or create a hash collision red-black tree realize the use of the HashMap. LinkedHashMap need only create override node, and when creating nodes, update the list storage order. Really is the multiplexing exploits to the extreme.

Delete nodes

As with the insert, delete LinkedHashMap parent class is also used to operate, and then override the callback methods afterNodeRemovalfor maintaining a doubly linked list.

// HashMap 中实现
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;
    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 (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            // ... 省略部分代码
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                                (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            // 删除后回调
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

// LinkedHashMap 中覆写
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    // 如果b为空,则为头节点
    if (b == null)
        head = a;
    else
        b.after = a;
    // 如果a为空,则为尾节点
    if (a == null)
        tail = b;
    else
        a.before = b;
}
复制代码

Maintenance of access order

If we in the initialization LinkedHashMap, the accessOrder parameter set to true, then we are only at the time of insertion maintains the list, while also maintaining access node list.

When we call get, getOrDefault, replaceupon other methods, will update the list, the access node is moved to the tail of the list.

// LinkedHashMap 中覆写
public V get(Object key) {
    Node<K,V> e;
    // 调用了 HashMap 中的 getNode 方法
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 如果accessOrder为true,调用afterNodeAccess
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

// LinkedHashMap 中覆写
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;
        // 如果b为空,则为头部
        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
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}
复制代码

Use the test code to experience the effects

public static void main(String[] args) {
    LinkedHashMap<String, Integer> lh = new LinkedHashMap<>(33, 0.75f, true);
    lh.put("one", 1);
    lh.put("two", 2);
    lh.put("three", 3);
    lh.put("four", 4);
    lh.get("two");
    for (String key : lh.keySet()) {
        System.out.println("key:" + key + "value:" + lh.get(key));
    }
}
复制代码

Even being given a

Look LinkedHashMap overwrite iterative code

final LinkedHashMap.Entry<K,V> nextNode() {
    LinkedHashMap.Entry<K,V> e = next;
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    if (e == null)
        throw new NoSuchElementException();
    current = e;
    next = e.after;
    return e;
}
复制代码

ConcurrentModificationExceptionThis is to prevent concurrent error conditions, while traversing the list changes occur. Because when we traverse in turn calls the get method, resulting in a change in the list, will throw this error.

accessOrder correct posture when traversing true follows, using LinkedHashMap overwrite forEachmethod, it will not modify the order in the list when the read value.

lh.forEach((String k, Integer v) -> {
    System.out.println("key:" + k + ", value:" + v);
});
复制代码

LinkedHashMap simple use of LRU

LRU stands for Least Recently Used, which is the least recently used meaning, a memory management algorithm that was first used in the Linux operating system.

This algorithm is based on the assumption: long-term data is not used, the chance of being used in the future is not great. Therefore, when the data reaches a certain threshold percentage of memory, we need to remove the least recently used data.

Here we introduce the pre-knowledge.

afterNodeInsertionIt is a callback method callback to insert elements of the time. LinkedHashMap override this method is mainly used to judge whether to remove the head of the list.

// LinkedHashMap 中覆写
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 根据条件判断是否移除链表的head节点
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

// LinkedHashMap 中实现,默认返回false
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}
复制代码

Below we will inherit LinkedHashMap, by overwriting removeEldestEntry, when the number of nodes reached Map exceeds a specified threshold value, the least access to delete nodes. In order to achieve LRU cache policy.

public class SimpleCache<K, V> extends LinkedHashMap<K, V> {

    private static final int MAX_NODE_NUM = 100;

    private int limit;

    public SimpleCache(){
        this(MAX_NODE_NUM);
    }

    public SimpleCache(int limit) {
        super(limit, 0.75f, true);
        this.limit = limit;
    }

    /**
     * 判断节点数是否超出限制
     * @param eldest
     * @return boolean
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > limit;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
        SimpleCache<Integer, Integer> cache = new SimpleCache<>(3);

        for (int i = 0; i < 10; i++) {
            cache.put(i, i * i);
        }

        System.out.println("插入10个键值对后,缓存内容:");
        System.out.println(cache);

        System.out.println("访问键值为7的节点后,缓存的内容:");
        cache.get(7);
        System.out.println(cache);

        System.out.println("插入键值为1的键值对后,缓存的内容:");
        cache.put(1, 1);
        System.out.println(cache);
    }
}
复制代码

Test results are as follows:

to sum up

This paper focuses on how to maintain a doubly linked list LinkedHashMap storage order unfolds, introduced inheritance LinkedHashMap and HashMap node classes, introduces new, delete, access, reuse, while LinkedHashMap how HashMap, maintenance doubly linked list. Finally, through inheritance LinkedHashMap very simple implementation of the LRU cache strategy.

More full amount of code, but are relatively easy to understand. JDK understanding of design ideas, explore the realization of the principle behind it, it is a very interesting thing.

The source code discussed in this article are based on JDK1.8 version.

Reference material

LinkedHashMap detailed source code analysis (JDK1.8)

[Articles] Getting Started with Java enhance Detailed Day28 Java container classes (X) LinkedHashMap Detailed

Source series

Java source code Series 1 - ArrayList

Java source code Series 2 - HashMap

Java source code Series 3 - LinkedHashMap

This article first appeared on my personal blog chaohang.top

Author Zhang super

Please indicate the source

I welcome attention to the micro-channel public number [Ultra] will not fly, get updates first time.

Guess you like

Origin juejin.im/post/5e527436e51d4526c932b406