Please talk about using LinkedHashMap to realize LRU algorithm cache

Posted by rosenrosen on Fri, 04 Mar 2022 02:54:38 +0100

1key and value are allowed to be null; 2key will be overwritten if repeated, and value can be repeated; 3. Orderly; 4LinkedHashMap is non thread safe;

1 LinkedHashMap can be considered as HashMap+LinkedList, that is, it uses HashMap to operate data structures and LinkedList to maintain the sequence of inserted elements 2 the implementation idea of LinkedHashMap is polymorphism. Understanding LinkedHashMap can help us deepen our understanding of polymorphism

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

It has two more attributes than HashMap:

//Head node of linked list
private transient Entry<K,V> header;
//This attribute refers to the method of obtaining key value pairs. It is a Boolean value: false indicates the insertion order; true indicates the access order;
private final boolean accessOrder;

LinkedHashMap has five constructors:

   	//Build a LinkedHashMap with the default initial capacity and load factor. The way to get the key value pairs is to insert the sequence
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    //Construct a LinkedHashMap with a specified initial capacity to obtain the order of key value pairs
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    //Construct a LinkedHashMap that specifies the initial capacity and load factor in the order of insertion
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    //According to the given initial capacity, load factor and key value pair iteration order, a LinkedHashMap is constructed
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

    //Create a LinkedHashMap through a given map, the load factor is the default value, and the iteration method is the insertion order
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }

It can be seen from the construction method that the insertion order is adopted by default to maintain the order of extracting key value pairs All construction methods build objects through the construction method of the parent class

The difference between LinkedHashMap and HashMap lies in their basic data organization. Let's take a look at the basic data structure Entry of LinkedHashMap:

  private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

next is used to maintain the order of entries connected at the specified table position of HashMap; Before and after are used to maintain the order of Entry insertion It is because of the existence of before, after and header that LinkedHashMap forms a circular two-way linked list It should be noted that the header node is an attribute of LinkedHashMap. It does not save the key value content. It is the Entry of a two-way linked list

LRU algorithm cache using LinkedHashMap

The so-called LRU:Least Recently Used, that is, when cached, it will give priority to those data that are not accessed frequently recently That is, cold data will be eliminated first
Let's take a look at a construction method of LinkedHashMap:

    //According to the given initial capacity, load factor and key value pair iteration order, a LinkedHashMap is constructed
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

In this construction method, there is an accessOrder. Its different values have different meanings:
false, all entries are arranged in the order of insertion
true, all entries are arranged in the order of access

Access order: if there are 1, 2 and 3 entries, then 1 is accessed and moved to the tail, that is, 2, 3 and 1. Move the accessed data to the end of the two-way queue every time you access it. When you want to eliminate the data every time, isn't the data at the top of the two-way queue the data you access the least often? In other words, the data at the head of the two-way linked list is the data to be eliminated.

import java.util.*;  
//Extend the LinkedHashMap class to implement the LRU algorithm  
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{  
    //Defines the capacity of the cache  
    private int capacity;  
    private static final long serialVersionUID = 1L;  
    //Constructor with parameters     
    LRULinkedHashMap(int capacity){  
        //Call the constructor of LinkedHashMap and pass in the following parameters  
        super(16,0.75f,true);  
        //Pass in the specified maximum cache capacity  
        this.capacity=capacity;  
    }  
    //The key method to realize LRU is to delete the top element of the linked list if the number of elements in the map is greater than the maximum cache capacity  
    @Override  
    public boolean removeEldestEntry(Map.Entry<K, V> eldest){   
        System.out.println(eldest.getKey() + "=" + eldest.getValue());    
        return size()>capacity;  
    }    
}  
//Test class  
class Test{  
public static void main(String[] args) throws Exception{  
  
    //Specifies that the maximum cache capacity is 4  
    Map<Integer,Integer> map=new LRULinkedHashMap<>(4);  
    map.put(9,3);  
    map.put(7,4);  
    map.put(5,9);  
    map.put(3,4);  
    map.put(6,6);  
    //A total of 5 elements were put, exceeding the specified maximum cache capacity  
    //Traversal result  
        for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){  
            System.out.println(it.next().getKey());  
        }  
    }  
}  

9=3
9=3
9=3
9=3
9=3
7
5
3
6