为了账号安全,请及时绑定邮箱和手机立即绑定

LRU相关

标签:
Java

    在刷leetcode的时候看到的自己设计一个LRU缓存,所以延伸的把LinkedHashMap看了一下。

    LinkedHashMap是HashMap的扩展,主要是利用双向链表实现保存插入顺序或者访问顺序的功能。

    LRU的话就是将accessOrder在初始化的时候传入

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

正常不传入就是false。如果为true的话,会发现在操作(get,put等)的时候都会调用一个方法:

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

就是将访问的这个节点往后放,所以最新访问的都是在最后的!LinkedHashMap里面的get和getOrDefault里面都判断的是否accessOrder为true,其实afterNodeAccess里面也判断了丫?而且HashMap里面putVal和replace之类的都直接调用了afterNodeAccess,但是get里面没有写,有点乱哦。。

如果你只传入了accessOrder,其实他是不会自己给你删除那些过期的节点的,你可以自己遍历实现删除头结点(最近最少使用的),你也可以实现方法removeEldestEntry,默认的是false

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

实现了这个方法以后,就可以在putVal以后,进行删除节点了

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    ...
    if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    ...
    afterNodeInsertion(evict);
    return null;
}
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);
    }
}
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
            ...
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}
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;
}

从源码就可以看出来了,在新增节点的时候,如果是以前存在的节点,会调用afterNodeAccess把他的顺序调整到最后。新增完成以后,调用afterNodeInsertion判断是不是达到了删除最早节点的要求,达到后就调用removeNode操作进行删除,也就是将hashMap中的节点删除(调整next指针),这个删除以后还要调用afterNodeRemoval将双向链表里面的删除(调整before和after指针),到此,就结束一次添加操作。

总结就是,添加的时候,先添加/调整顺序,再删除;获取的时候,调整顺序。

然后我看了几个其他开源项目的LRUCache实现,好像差不多都是利用了LinkedHashMap

com.mysql.jdbc.util

public class LRUCache extends LinkedHashMap<Object, Object> {
    private static final long serialVersionUID = 1L;
    protected int maxElements;

   public LRUCache(int maxSize) {
      super(maxSize, 0.75F, true);
      this.maxElements = maxSize;
   }

   /*
    * (non-Javadoc)
    * 
    * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
    */
   @Override
   protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
      return (size() > this.maxElements);
   }
}

很简单,就是写了一下removeEldestEntry方法。

com.alibaba.dubbo.common.utils

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = -5167631809472116969L;
    private static final float DEFAULT_LOAD_FACTOR = 0.75F;
    private static final int DEFAULT_MAX_CAPACITY = 1000;
    private volatile int maxCapacity;
    private final Lock lock;

    public LRUCache() {
        this(1000);
    }

    public LRUCache(int maxCapacity) {
        super(16, 0.75F, true);
        this.lock = new ReentrantLock();
        this.maxCapacity = maxCapacity;
    }

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

    public boolean containsKey(Object key) {
        boolean var2;
        try {
            this.lock.lock();
            var2 = super.containsKey(key);
        } finally {
            this.lock.unlock();
        }

        return var2;
    }
    ...
    
  // 也是比较简单的,就是因为hashmap是非线程安全的,所以在操作的时候都加了锁,保证了线程安全性。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消