LRUMAP 原理解析

本文介绍了Java中LRUMap(Least Recently Used Map)的实现原理和使用方法,它是一种基于链表的数据结构实现的Map,用于在达到预设容量时淘汰最近最少使用的数据。LRUMap在添加元素时会执行淘汰算法,通过更新链接表的顺序来维护最近使用状态。文章还展示了如何在多线程环境下确保其线程安全性,并给出了简单的使用示例和关键代码解析。
摘要由CSDN通过智能技术生成

作者:Charles,转载时请指明作者出处

背景:

在JDK的集合结构里面,我们用各种各样的map结构,例如HashMap,TreeMap,LInkedHashMap. ConcurrentHashMap等,不同的Map结构实际上是一种特殊的数据结构,来满足我们实际的业务需求,今天给大家介绍一种LRUMAP的实现。

Map 主要是实现快速随机的存储的key-value映射的结构,而LRUMAP是在有限的集合里面,

如果存储的时候,集合超出了限制,那么就淘汰最近最少使用的数据,实际上是一种资源调度的算法。操作系统的内存管理实际上就是一种LRU调度,在我们的程序中,如果想使用本地缓存来实现LRU的调度,使用apache common-collections框架中的LRUMAP不失为一种很好的选择,我们来分析下LRUMAP的实现原理。

使用方法:

LRUMap 的一个简单使用示例:

public static void main(String[] args) {
    LRUMap lruMap = new LRUMap(2);
    lruMap.put("a1", "1");
    lruMap.put("a2", "2");
    lruMap.get("a1");//mark as recentused
    lruMap.put("a3", "3");
    System.out.println(lruMap);
}

上面的示例,当增加”a3”值时,会淘汰最近最少使用的”a2”, 最后输出的结果为:

{a1=1,a3=3}

实现原理:

LRUMap则是实现的LRP算法的Map集合类,它继承于AbstractLinkedMap抽象类。

LRUMap的使用说明如下:

LRUMap的初始化时需要指定最大集合元素个数,新增的元素个数大于允许的最大集合个数时,则会执行LRU淘汰算法。所有的元素在LRUMap中会根据最近使用情况进行排序。最近使用的会放在元素的最前面(LRUMap是通过链表来存储元素内容). 所以LRUMap进行淘汰时只需要删除链表最后一个即可(即header.after所指的元素对象)

那么那些操作会影响元素的使用情况:

1.put 当新增加一个集合元素对象,则表示该对象是最近被访问的

2. get 操作会把当前访问的元素对象作为最近被访问的,会被移到链接表头

注:当执行containsKey和containsValue操作时,不会影响元素的访问情况。

LRUMap也是非线程安全。在多线程下使用可通过Collections.synchronizedMap(Map)操

作来保证线程安全。

LRUMap类的关键代码说明如下:

1.get操作

public Object get(Object key) {
        LinkEntry entry = (LinkEntry) getEntry(key);
        if (entry == null) {
            return null;
        }
        moveToMRU(entry);//执行LRU操作
        return entry.getValue();
    }          

moveToMRU方法代码如下:

 protected void moveToMRU(LinkEntry entry) {
        if (entry.after != header) {
            modCount++;
            // remove 从链接中移除当前点
            entry.before.after = entry.after;
            entry.after.before = entry.before;
            // add first 把节点增加到链接头部
            entry.after = header;
            entry.before = header.before;
            header.before.after = entry;
            header.before = entry;
        } else if (entry == header) {
            throw new IllegalStateException("Can't move header to MRU" +
                " (please report this to commons-dev@jakarta.apache.org)");
        }
}

2. put新增操作

由于继承的AbstractLinkedMap,所以put操作会调用addMapping方法,代码如下:

    protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
        if (isFull()) {
            LinkEntry reuse = header.after;
            boolean removeLRUEntry = false;
            if (scanUntilRemovable) {
                while (reuse != header && reuse != null) {
                    if (removeLRU(reuse)) {
                        removeLRUEntry = true;
                        break;
                    }
                    reuse = reuse.after;
                }
                if (reuse == null) {
                    throw new IllegalStateException(
                        "Entry.after=null, header.after" + header.after + " header.before" + header.before +
                        " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
                        " Please check that your keys are immutable, and that you have used synchronization properly." +
                        " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
                }
            } else {
                removeLRUEntry = removeLRU(reuse);
            }
            
            if (removeLRUEntry) {
                if (reuse == null) {
                    throw new IllegalStateException(
                        "reuse=null, header.after=" + header.after + " header.before" + header.before +
                        " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
                        " Please check that your keys are immutable, and that you have used synchronization properly." +
                        " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
                }
                reuseMapping(reuse, hashIndex, hashCode, key, value);
            } else {
                super.addMapping(hashIndex, hashCode, key, value);
            }
        } else {
            super.addMapping(hashIndex, hashCode, key, value);
        }
    }

当集合的个数超过指定的最大个数时,会调用reuseMapping函数,把要删除的对象的key和value更新为新加入的值,并再次加入到链接表中,并重新排定次序。

reuseMapping函数代码如下:

    protected void reuseMapping(LinkEntry entry, int hashIndex, int hashCode, Object key, Object value) {
        // find the entry before the entry specified in the hash table
        // remember that the parameters (except the first) refer to the new entry,
        // not the old one
        try {
            int removeIndex = hashIndex(entry.hashCode, data.length);
            HashEntry[] tmp = data;  // may protect against some sync issues
            HashEntry loop = tmp[removeIndex];
            HashEntry previous = null;
            while (loop != entry && loop != null) {
                previous = loop;
                loop = loop.next;
            }
            if (loop == null) {
                throw new IllegalStateException(
                    "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous +
                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
                    " Please check that your keys are immutable, and that you have used synchronization properly." +
                    " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
            }
            
            // reuse the entry
            modCount++;
            removeEntry(entry, removeIndex, previous);
            reuseEntry(entry, hashIndex, hashCode, key, value);
            addEntry(entry, hashIndex);
        } catch (NullPointerException ex) {
            throw new IllegalStateException(
                    "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) +
                    " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
                    " Please check that your keys are immutable, and that you have used synchronization properly." +
                    " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
        }
    }

removeEntry 方法是删除最近最少使用的节点在链接中的引用

reuseEntry方法则把该节点的key和value赋新加入的节点的key和value值

addEntry方法则把该节点加入到链接表,并保障相关的链接顺序

    /**
     * Adds an entry into this map, maintaining insertion order.
     * <p>
     * This implementation adds the entry to the data storage table and
     * to the end of the linked list.
     * 
     * @param entry  the entry to add
     * @param hashIndex  the index into the data array to store at
     */
    protected void addEntry(HashEntry entry, int hashIndex) {
        LinkEntry link = (LinkEntry) entry;
        link.after  = header;
        link.before = header.before;
        header.before.after = link;
        header.before = link;
        data[hashIndex] = entry;
    }

总结:

LRUMap的使用场景会比较多,例如可以很方便的帮我们实现基于内存的LRU 缓存服务实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值