基于linkedHashMap实现的LRU算法

LRU全称是Least Recently Used,即最近最久未使用的意思。
LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
解决的实际问题:当做数据缓存时,缓存的数据会随着时间的推移越来越多,如果没有缓存清除策略,那么会出现俩个问题:1、缓存越来越大挤爆内存。2、很多不使用的数据占据这内存空间,导致内存得不到有效利用。
此场景使用LRU算法非常合适。
LRU算法的主要思想:1.设置一个缓存阈值,超过阈值删除最老的数据。
2.保证最老的数据总是在链表的头部,最新的数据总是在尾部,这样每次需要删除数据时把头部数据删除即可。
linkedHashMap对LRU算法的实现:
1.HashMap中的三个没实现的方法,在linkedHashMap实现:

//把新插入的元素放到链表尾部
 void afterNodeAccess(Node<K,V> p) { }
 //在放入新元素后,检查是否要删除最老的元素,需要则删除
 void afterNodeInsertion(boolean evict) { }
 //在删除元素之后调整头尾部元素
  void afterNodeRemoval(Node<K,V> p) { }

2.实现思路:
1.在put方法时:

 if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

如果新加的是已经存在于链表中的,那么把元素调整到链表尾部,以保证最近数据都在尾部。

 afterNodeInsertion(evict);

如果新增数据不是旧数据,那么调用afterNodeInsertion方法,检查是否要删除最老的元素,需要则删除。afterNodeInsertion在linkedHashMap中的实现:

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

可以看出,删除老数据的条件中最重要的是removeEldestEntry方法的返回值为true。

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

此方法默认的返回值是false,说明linkedHashMap默认是不会删除老数据的,真正要实现LRU算法得自己重写此方法。
如果满足删除最老数据的条件,那么会调用removeNode方法:

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 (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.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;
    }

可以看到在删除数据之后会调用afterNodeRemoval方法:

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;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

根据删除节点的前后节点,重新确定链表的顺序,以保证链表的连续性。
总结:在链表中新增数据时,首先判断数据是否是老数据,是的话把数据调整到链表尾部,以保证最新数据一直在尾部。不是的话在数据加入之后,判断是否需要删除最老的数据,需要则删除。
通过前面的分析,要真正实现LRU算法需要重写removeEldestEntry方法。在druid 的jar包中已有了相关实现:

package com.alibaba.druid.util;

import java.util.LinkedHashMap;
import java.util.Map;

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

    private static final long serialVersionUID = 1L;
    private final int         maxSize;

    public LRUCache(int maxSize){
        this(maxSize, 16, 0.75f, false);
    }

    public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder){
        super(initialCapacity, loadFactor, accessOrder);
        this.maxSize = maxSize;
    }

    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return this.size() > this.maxSize;
    }
}

而且redis也对LRU算法有相关的支持,修改redis.conf:

# maxmemory <bytes>

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.

maxmemory 设置最大存储量,超过存储量后使用相关策略进行回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值