43

LinkedHashMap中LRU算法实现

 6 years ago
source link: http://tech.dianwoda.com/2018/09/04/linkedhashmapzhong-lrude-shi-xian/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们都知道LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。实际上,Redis缓存和MyBatis二级缓存更新策略算法中就有LRU。

分析LinkedHashMap中的LRU

其实一提到LRU,我们就应该想到LinkedHashMap。LRU是通过双向链表来实现的。当某个位置的数据被命中,通过调整该数据的位置,将其移动至尾部。新插入的元素也是直接放入尾部(尾插法)。这样一来,最近被命中的元素就向尾部移动,那么链表的头部就是最近最少使用的元素所在的位置。

HashMap的afterNodeAccess()、afterNodeInsertion()、afterNodeRemoval()方法都是空实现,留着LinkedHashMap去重写。LinkedHashMap靠重写这3个方法就完成了核心功能的实现。不得不感叹,LinkedHashMap设计之妙。

// Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }
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;
    }

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

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

在LinkedHashMap的get()方法中,我们每次获取元素的时候,都要调用afterNodeAccess(e)都要将元素移动到尾部。accessOrder为true,是基于访问排序,accessOrder为基于插入排序。我们想要LinkedHashMap实现LRU功能,accessOrder必须为true。如果accessOrder为false,那就是FIFO了。

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

我们可以看到插入数据的时候,如果removeEldestEntry(first)返回true,按照LRU策略,那么会删除头节点。

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

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

LinkedHashMap大体的LRU架子都为我们搭好了。那我们怎么去基于LinkedHashMap实现LRU呢。先别慌,我们先看看Mysql-jdbc中的LruCache是怎么实现的。

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

    protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
        return this.size() > this.maxElements;
    }
}

其实我们只要把accessOrder设置为true,重写removeEldestEntry(eldest)即可。我们在removeEldestEntry(eldest)加上什么时候执行LRU操作的逻辑,比如map里面的元素数量超过指定的大小,开始删除最近最少使用的元素,为后续新增的元素腾出位置来。

结语

希望我的文章对你能有所帮助,谢谢


Recommend

  • 47
    • blog.csdn.net 6 years ago
    • Cache

    漫画:什么是LRU算法?

    点击上方“ 程序人生 ”,选择“置顶公众号” 第一时间关注程序猿(媛)身边的故事 述者

  • 19
    • labuladong.github.io 4 years ago
    • Cache

    算法就像搭乐高:带你手撸 LRU 算法

    层层拆解,带你手写 LRU 算法 👆让天下没有难刷的算法!若 GitBook 访问太慢,可尝试

  • 9
    • www.vcjmhg.top 4 years ago
    • Cache

    手写LRU缓存淘汰算法

    LRU 算法全称为 Least Recently Used 是一种常见的页面缓存淘汰算法,当缓存空间达到达到预设空间的情况下会删除那些最久没有被使用的数据 。 常见的页面缓存淘汰算法主要有一下几种: LRU 最近最久未使用

  • 13
    • blog.ponder.work 3 years ago
    • Cache

    缓存淘汰算法之LRU

    说到缓存,就必须先了解下计算机的存储器层次结构。 存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。 计算机系统中的存储设备都被组织成了一个存储器层次结构,从上至下,设备的访问速度越来越慢、容量...

  • 15
    • my.oschina.net 3 years ago
    • Cache

    web技术分享| LRU 缓存淘汰算法

    了解 LRU 之前,我们应该了解一下缓存,大家都知道计算机具有缓存内存,可以临时存储最常用的数据,当缓存数据超过一定大小时,系统会进行回收,以便释放出空间来缓存新的数据,但从系统中检索数据的成本比较高。 缓存要求:

  • 5
    • allenwind.github.io 3 years ago
    • Cache

    LRU算法的实现(Python版)

    Mr.Feng BlogNLP、深度学习、机器学习、Python、GoLRU算法的实现(Python版)上文Go现LRU算法,给出了Go...

  • 3
    • allenwind.github.io 3 years ago
    • Cache

    Go实现LRU算法

    Go实现LRU算法LRU算法是一种页面置换算法。常见的页面置换算法包括:random、FIFO、LRU、LFU。 采用随机random的置换算法效率并不好,没有利用好页面置换调度序列的信息,更加没有考虑程序的局部性原理。 FIFO实现简单,但没有利用好程...

  • 19
    • allenwind.github.io 3 years ago
    • Cache

    LRU算法的实现(Java版)

    Mr.Feng BlogNLP、深度学习、机器学习、Python、GoLRU算法的实现(Java版)前文写到Go和Python实现LRU算法及LRU算法的相关讨论,本文讲述使用Java实现LRU算法。Ja...

  • 6
    • murphypei.github.io 3 years ago
    • Cache

    LRU 缓存算法

    LRU 缓存算法 发表于 2021-07-07 ...

  • 9
    • helloteemo.github.io 3 years ago
    • Cache

    LRU算法实现

    LRU算法实现 发表于 2021-07-20 更新于 2021-09-20 分类于 Redis 阅读次数: 16 本文字数: 4.5k 阅读时长 ≈ 4 分钟介绍了Redis最常用的缓存淘汰策略LRU(最...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK