JDK源码----如何利用LinkedHashMap实现LRU

如何利用LinkedHashMap实现LRU


前言

之前我有篇文章是手写实现了LRU,那么如何利用JDK自带的实现呢?本篇带你了解下


提示:需要对LinkedHashMap有了解才行,建议先看源码

一、思路分析

首先我们看下

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

只有这个构造方法可以使这个成员变量为true。那么这个是什么意思呢?
默认的LinkedHashMap的访问顺序是添加顺序,当用此构造方法,即可改变结构,比如,添加时顺序为:a,b,c,d,e。当我调用get(a)时,结构变为
b,c,d,e,a

利用此特性再加上

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

我们要重写此方法才行,为什么我们接着分析
我们看下HashMap的put

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //中间的添加过程我就省略了,之前文章分析过
        ++modCount;//替换的不算
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

afterNodeInsertion(evict);这个方法当我传入参数是true时。
我们来到HashMap的子类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);
        }
    }

evict为true,removeEldestEntry(first)这个我们可以重写使其为true。那么我们就可以删除头节点。

综上我们就利用了两条特性实现了HashMap

  1. LinkedHashMap的成员变量accessOrder在构造时设置为true,使其按访问顺序排列。访问到的放在末尾。
  2. 重写removeEldestEntry方法。比如大于缓存最大长度

二、代码

最后贴上完整代码

package HashTest;

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

public class LRU<K,V> extends LinkedHashMap<K, V> implements Map<K, V> {

    private static final long serialVersionUID = 1L;

    public LRU(int initialCapacity,
               float loadFactor,
               boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    /**
     * @description 重写LinkedHashMap中的removeEldestEntry方法,当LRU中元素多余6个时,
     *              删除最不经常使用的元素
     * @param eldest
     * @return
     * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
     */
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        if(size() > 6){
            return true;
        }
        return false;
    }

    public static void main(String[] args) {

        LRU<Character, Integer> lru = new LRU<Character, Integer>(
                16, 0.75f, true);

        String s = "abcdefghijkl";
        for (int i = 0; i < s.length(); i++) {
            lru.put(s.charAt(i), i);
        }
        System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));
        System.out.println("LRU的大小 :" + lru.size());
        System.out.println("LRU :" + lru);
    }
}

总结

平时工作上不要仅仅会用,还是要多看源码才能了解的更深,例如本篇文章的实现,如果对源码不了解是不会有这种思路的。

另外再说一下
这四个方法保证新添加的元素永远放到最后

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        LinkedHashMap.Entry<K,V> t =
            new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }

    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p);
        return p;
    }

    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }

补充扩展

基于过期时间的LRU缓存map

package algorithm;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class LocalCache {
    //默认的缓存容量
    private static int DEFAULT_CAPACITY = 16;
    //最大容量
    private static int MAX_CAPACITY = 64;
    private ReentrantLock lock = new ReentrantLock();

    private static Map<String, CacheData> linkedHashMap = new LinkedHashMap<String, CacheData>(DEFAULT_CAPACITY, 0.75f, true) {

        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            //缓存淘汰
            return MAX_CAPACITY < linkedHashMap.size();
        }
    };
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    public void put(String key, String value, long expire) {
        lock.lock();
        try {
            CacheData data = new CacheData();
            data.setKey(key);
            data.setValue(value);
            data.setExpireTime(expire + System.currentTimeMillis());
            linkedHashMap.put(key, data);
            if (expire > 0) {
                removeAfterExpireTime(key, expire);
            }
        } finally {
            lock.unlock();
        }
    }
    private void removeAfterExpireTime(String key, long expireTime) {
        scheduledExecutorService.schedule(() -> {
            System.out.println("过期后清除该键值对");
            linkedHashMap.remove(key);
        }, expireTime, TimeUnit.MILLISECONDS);
    }
    /**
     * 过期删除
     */
    public CacheData get(String key) {
        lock.lock();
        try {
            return linkedHashMap.getOrDefault(key, new CacheData());
        } finally {
            lock.unlock();
        }
    }

    private static class CacheLocal {
        private static LocalCache cache = new LocalCache ();
    }

    public static LocalCache getInstance() {
        return CacheLocal.cache;
    }

    private class CacheData {

        private String key;

        private String value;

        /**
         * 存活时间
         */
        private long expireTime;


        private String getValue() {
            return value;
        }

        private void setValue(String value) {
            this.value = value;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public long getExpireTime() {
            return expireTime;
        }

        public void setExpireTime(long expireTime) {
            this.expireTime = expireTime;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值