手撸LRU算法(java实现)

Posted 快乐崇拜234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸LRU算法(java实现)相关的知识,希望对你有一定的参考价值。

正文在下面,先打个广告:

使用javaAPI

java 的LinkedHashMap可以实现LRU算法,LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素)。我们只需要通过参数控制数据排序逻辑和数据删除规则就行了。

LinkedHashMap源码中有这么一个注释:

<p>The @link #removeEldestEntry(Map.Entry) method may be overridden to
 * impose a policy for removing stale mappings automatically when new mappings
 * are added to the map.

可以看到 removeEldestEntry 方法可以用来删除过时的数据。不过他的默认实现是返回false,需要我们手动重写该方法。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) 
 return false;

还有一个需要注意的是,LinkedHashMap默认是按照插入顺序排序的,很显然不能满足LRU算法的需求。LRU是希望按照访问顺序排序,最新访问的数据放到队头,老数据放到队尾,这样数据总量超过容量时,删除队尾的数据即可。

LinkedHashMap构造函数中有一个参数accessOrder,该参数控制了数据排序的规则。下面get方法中可以看到,有个if判断,如果accessOrder是true,则将当前访问的node移动到队尾last(LinkedHashMap是用last存储最新数据)

/**
 * Constructs an empty <tt>LinkedHashMap</tt> instance with the
 * specified initial capacity, load factor and ordering mode.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @param  accessOrder     the ordering mode - <tt>true</tt> for
 *         access-order, <tt>false</tt> for insertion-order
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) 
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;


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;
        
    

当插入数据时,就会通过removeEldestEntry方法的返回值决定是否需要删除老的数据。

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

完整代码:

public interface ILru 
    void put(Object key, Object value);

    void remove(String key);

    Object get(String key);

public class JavaLRUCache implements ILru

    private Map<Object, Object> map;
    private final int capacity;

    public JavaLRUCache(int capacity) 
        this.capacity = capacity;
        map = new LinkedHashMap<Object, Object>(capacity, 0.75f, true) 
            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) 
                return size() > capacity;
            

            @Override
            public String toString() 
                return super.toString();
            
        ;
    

    @Override
    public Object get(String key) 
        return map.get(key);
    

    @Override
    public void put(Object key, Object value) 
        map.put(key, value);
    

    @Override
    public void remove(String key) 
        map.remove(key);
    

    @Override
    public String toString() 
        return "JavaLRUCache" +
                "map=" + map +
                ", capacity=" + capacity +
                '';
    

纯手撸

纯手撸的话,其实和上面LinkedHashMap原理一样。
先定义node

/**
 * @author liubenlog
 * @className Node
 * @description map 中的V,设置为一个对象,主要是为了记录前后指针,已达到 0(1)的时间复杂度
 * @date 2020/10/22 16:02
 */
@Data
public class Node 
    private Object key;
    private Object value;
    private Node pre;
    private Node next;

    public Node(Object key, Object value)
        this.key=key;
        this.value=value;
    

    @Override
    public String toString() 
        return key.toString() + "=" + value.toString();
    


再定义链表,包含LRU的核心算法

/**
 * @author liubenlog
 * @className MyList
 * @description 双向链表. 我这里规定head是最新的数据
 * @date 2020/10/22 16:03
 */
public class MyList 

    private long maxSize;

    private Node head;

    private Node tail;

    private long size;

    private Map map;

    public MyList(long maxSize, Map map) 
        this.maxSize = maxSize;
        this.map = map;
    

    /**
     * 直接添加到tail
     *
     * @param node
     */
    public void add(Node node) 
        size++;
        if (null == head) 
            //head为空,说明此时队列中一个数据也没有
            head = node;
            tail = node;
         else 
            node.setPre(tail);
            tail.setNext(node);
            tail = node;

            //超过容量后,将head元素删除
            if (size > maxSize) 
                map.remove(head.getKey());

                Node next = head.getNext();
                next.setPre(null);
                head.setNext(null);
                head = next;
                size--;
            
        
    

    public void remove(Node node) 
        if (null == head) //队列为空
            return;
         else if (head == node && tail == node) //只有一个元素时
            head = null;
            tail = null;
            size--;
         else 
            Node pre = node.getPre();
            Node next = node.getNext();
            if (null != pre) 
                pre.setNext(next);
             else 
                head = next;
            
            if (null != next) 
                next.setPre(pre);
             else 
                tail = pre;
            
            node.setPre(null);
            node.setNext(null);
            size--;
        
    

    public void get(Node node) 
        remove(node);
        add(node);
    

    public void update(Node node) 
        remove(node);
        add(node);
    

    @Override
    public String toString() 
        String result = "";
        if (head == null) 
            return result;
         else 
            Node next = head;
            result += next + ", ";
            while ((next = next.getNext()) != null) 
                result += next + ", ";
            
            return result;
        
    


最后封装LRU

public class LRU implements ILru
    private HashMap<Object, Node> map = new HashMap();
    private MyList list;

    public LRU(long size) 
        list = new MyList(size, map);
    

    @Override
    public void put(Object key, Object value) 
        Node node = map.get(key);
        if (node == null) 
            node = new Node(key, value);
            map.put(key, node);
            list.add(node);
         else 
            node.setValue(value);
            map.put(key, node);
            list.update(node);
        
    

    @Override
    public void remove(String key) 
        list.remove(map.get(key));
        map.remove(key);
    

    @Override
    public Object get(String key) 
        Node node = map.get(key);
        if (node == null) 
            return null;
        
        list.get(node);

        return node.getValue();
    

    @Override
    public String toString() 
        return "LRU" +
                "list=" + list +
                '';
    

测试

测试代码

public class Main 
    public static void main(String[] args) 
        lruTest(new LRU(5));
        System.out.println("-------------------------");
        lruTest(new JavaLRUCache(5));
    

    static void lruTest(ILru lru)
        lru.put("a", 1);
        System.out.println(lru.toString());
        lru.put("b", 2);
        System.out.println(lru.toString());
        lru.put("c", 3);
        System.out.println(lru.toString());
        lru.put("d", 4);
        System.out.println(lru.toString());
        lru.get("a");
        System.out.println("get a 1  " + lru.toString());
        lru.get("b");
        System.out.println("get b 2  " + lru.toString());
        lru.get("b");
        System.out.println("get b 2  " + lru.toString());
        lru.get("d");
        System.out.println("get d 4  " + lru.toString());
        lru.put("e", 5);
        System.out.println(lru.toString());
        lru.put("f", 6);
        System.out.println(lru.toString());
        lru.put("g", 7);
        System.out.println(lru.toString());
        lru.remove("g");
        System.out.println("remove g 7  " + lru.toString());
        lru.remove("e");
        System.out.println("remove e 5  " + lru.toString());
        lru.put("e", 5);
        System.out.println(lru.toString());
        lru.put("e", 9);
        System.out.println(lru.toString());
        lru.put("f", "f");
        System.out.println(lru.toString());
    

输出:

LRUlist=a=1, 
LRUlist=a=1, b=2, 
LRUlist=a=1, b=2, c=3, 
LRUlist=a=1, b=2, c=3, d=4, 
get a 1  LRUlist=b=2, c=3, d=4, a=1, 
get b 2  LRUlist=c=3, d=4, a=1, b=2, 
get b 2  LRUlist=c=3, d=4, a=1, b=2, 
get d 4  LRUlist=c=3, a=1, b=2, d=4, 
LRUlist=c=3, a=1, b=2, d=4, e=5, 
LRUlist=a=1, b=2, d=4, e=5, f=6, 
LRUlist=b=2, d=4, e=5, f=6, g=7, 
remove g 7  LRUlist=b=2, d=4, e=5, f=6, 
remove e 5  LRUlist=b=2, d=4, f=6, 
LRUlist=b=2, d=4, f=6, e=5, 
LRU以上是关于手撸LRU算法(java实现)的主要内容,如果未能解决你的问题,请参考以下文章

其实吧,LRU也就那么回事。

手撸一致性hash算法(java实现)

LRU算法java实现

LRU算法java实现

基础算法Java实现LRU算法

Java实现一个LRU算法