The largest data structure in Java: LinkedHashMap?

Author: Monarudo
https://www.jianshu.com/p/06a0fd962e0b

There are many Map families, of which HashMap And Concurrent HashMap are the most used, while LinkedHashMap seems to be less useful, but it has a sequence.There are two types, one is the adding order, the other is the access order.

LinkedHashMap inherits HashMap .So if it's you, how do you achieve these two orders?

If the order of additions is implemented, we can add a chain table to this class where each node corresponds to a bucket in the hash table.This way, when the loop is traversed, it can be traversed according to the list of chains.It just increases memory consumption.

Chain lists can also be used if access order is implemented, but each time data is read, the list needs to be updated to place the last read at the end of the chain.This will also be possible.This feature can also be followed to implement LRU (Least Recently Used) caching.

How to use it?

Here's a little demo

LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
for (int i = 0; i < 10; i++) {
  map.put(i, i);
}

for (Map.Entry entry : map.entrySet()) {
  System.out.println(entry.getKey() + ":" + entry.getValue());
}
map.get(3);
System.out.println();
for (Map.Entry entry : map.entrySet()) {
  System.out.println(entry.getKey() + ":" + entry.getValue());
}

Print results:

0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9

0:0
1:1
2:2
4:4
5:5
6:6
7:7
8:8
9:9
3:3

The first construction method is interesting, with one more accessOrder boolean parameter than HashMap.Represents sorting by order of access.Latest visits are placed at the end of the list.

If it is default, it is added in the order that accessOrder defaults to false.

Source implementation

If you look at the LinkedHashMap internal source code, you can see that there is actually a chain table maintained internally:

/**
 * Header of a two-way Chain table, oldest accessed
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The end of a two-way Chain table, newly accessed
 */
transient LinkedHashMap.Entry<K,V> tail;

And this LinkedHashMap.Entry Internally, the elements necessary for a two-way chain table are also maintained, before, after:

/**
 * HashMap.Node subclass for normal LinkedHashMap entries.
 */
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);
    }
}

When an element is added, it is appended to the end.

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

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

At get time, the chain order is modified according to the accessOrder property:

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        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;
    }
}

Also note: modCount has been modified here, and concurrency is not safe even for read operations.

How do I implement LRU caching?

LRU Cache: LRU (Least Recently Used, least recently used) The algorithm eliminates data based on the historical access history of the data, with the core idea that if the data has been accessed recently, it will have a higher chance of being accessed in the future.

LinkedHashMap doesn't help us achieve something specific. We need to do it ourselves.The implementation is the removeEldestEntry method.

Let's see how this works.

First, HashMap calls the afterNodeInsertion method at the end of the putVal method, which is actually reserved for LinkedHashMap.The LinkedHashMap implementation determines if the head node needs to be deleted based on some criteria.

Focus on WeChat Public Number: Java Technology Stack, reply in the background: Java, you can get the N latest Java technology tutorials I organized, they are all dry goods.

The source code is as follows:

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

The evict parameter indicates whether an element needs to be deleted, and this if determines if the condition to be met is as follows: the head cannot be null, call the removeEldestEntry method, and return true, then delete the head.This method returns false by default, waiting for you to rewrite it.

Therefore, the implementation of the removeEldestEntry method is usually as follows:

public boolean removeEldestEntry(Map.Entry<K, V> eldest){
   return size() > capacity;
}

If the length is larger than the capacity, you will need to clear the cache that is not accessed frequently.afterNodeInsertion calls the removeNode method and deletes the head er node, which is the least frequently visited node if accessOrder is true.

Pick Up Lost Items

LinkedHashMap rewrote some HashMap  For example, the containsValue method, which you can guess, how to override is reasonable?

HashMap  Using a double loop, the outer hash table is looped first, and the inner entry chain table is looped again.Performance is conceivable.

However, LinkedHashMap has an element list inside, just traverse the list directly.Relatively much higher.

public boolean containsValue(Object value) {
    for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
        V v = e.value;
        if (v == value || (value != null && value.equals(v)))
            return true;
    }
    return false;
}

It's also a space-to-time strategy.

The get method is, of course, overridden.Because the chain table needs to be updated based on the accessOrder.

summary

Summary of Xuewei:

LinkedHashMap contains a two-way chain table maintenance order that supports two orders - add order and access order.

The default is in the order of additions, and accessOrder in the construction method needs to be set to true if the access order is to be changed.This way, each time the get method is called, the element you just visited is updated to the end of the list.

With respect to LRU, in the mode where accessOrder is true, you can override the removeEldestEntry method and return size () > capacity so that you can delete the elements that are least frequently accessed.

Recommend going to my blog to read more:

1.Java JVM, Collections, Multithreaded, New Features Series Tutorial

2.Spring MVC, Spring Boot, Spring Cloud series tutorials

3.Maven, Git, Eclipse, Intellij IDEA Series Tools Tutorial

4.Latest Interview Questions for Java, Backend, Architecture, Alibaba, etc.

Feel good, don't forget to say yes + forward!

Tags: Java Spring less jvm

Posted on Tue, 16 Jun 2020 21:01:57 -0400 by aboldock