手写一个LruCache

代码实现一 (直接继承ListHashMap.java)

为了实现缓存回收,我们需要很容易做到:

  • 查询出最近最晚使用的项
  • 给最近使用的项做一个标记


链表可以实现这两个操作。检测最近最少使用的项只需要返回链表的尾部。标记一项为最近使用的项只需要从当前位置移除,然后将该项放置到头部。比较困难的事情是怎么快速的在链表中找到该项。

哈希表的帮助

看一下我们工具箱中的数据结构,哈希表可以在(消耗)常量的时间内索引到某个对象。如果我们创建一个形如key->链表节点的哈希表,我们就能够在常量时间内找到最近使用的节点。更甚的是,我们也能够在常量时间内判断节点的是否存在(或不存在);

找到这个节点后,我们就能将这个节点移动到链表的最前端,标记为最近使用的项了。

Java的捷径

据我所知,很少有一种编程语言的标准库中有通用的数据结构能提供上述功能的。这是一种混合的数据结构,我们需要在哈希表的基础上建立一个链表。但是Java已经为我们提供了这种形式的数据结构-LinkedHashMap!它甚至提供可覆盖回收策略的方法(见removeEldestEntry文档)。唯一需要我们注意的事情是,改链表的顺序是插入的顺序,而不是访问的顺序。但是,有一个构造函数提供了一个选项,可以使用访问的顺序(见文档)。

 

代码实现一 (直接继承ListHashMap.java)

package org.java.write.lru;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

    private final int maxCapacity;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    private final Lock lock = new ReentrantLock();

    public LRULinkedHashMap(int maxCapacity) {
        super(maxCapacity, DEFAULT_LOAD_FACTOR, true);
        this.maxCapacity = maxCapacity;
    }


    //重新removeEldestEntry 方法 当前size大于maxCapacity时 删除最老的数据
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxCapacity;
    }

    @Override
    public boolean containsKey(Object key) {
        try {
            lock.lock();
            return super.containsKey(key);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public V get(Object key) {
        try {
            lock.lock();
            ;
            return super.get(key);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public V put(K key, V value) {
        try {
            lock.lock();
            return super.put(key, value);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int size() {
        try {
            lock.lock();
            return super.size();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void clear() {
        try {
            lock.lock();
            super.clear();
        } finally {
            lock.unlock();
        }
    }

    public Collection<Map.Entry<K, V>> getAll() {
        try {
            lock.lock();
            return new ArrayList<Map.Entry<K, V>>(super.entrySet());
        } finally {
            lock.unlock();
        }
    }

}

代码实现二

package org.java.write.lru;

import java.util.HashMap;
import java.util.LinkedList;

public class LruCache<K, V> {
    private int currentCacheSize;
    private int cacheCapacity;
    private HashMap<K, CacheNode<K, V>> caches;
    private CacheNode<K, V> first;
    private CacheNode<K, V> last;

    public LruCache(int size) {
        currentCacheSize = 0;
        this.cacheCapacity = size;
        caches = new HashMap<K, CacheNode<K, V>>(size);
    }

    public void put(K k, V v) {
        CacheNode<K, V> node = caches.get(k);
        //等于null 则代表缓存里没有数据 需要进行缓存
        if (node == null) {
            //如果超出范围 超出需要移除最后一个
            if (caches.size() >= cacheCapacity) {
                caches.remove(last.key);
                removeLast();
            }
        }
        //构建cacheNode  新构建的节点 应该是
        node = new CacheNode<K, V>();
        node.key = k;
        node.value = v;
        moveToFirst(node);
        caches.put(k, node);
        currentCacheSize++;
    }

    public V get(K k) {
        CacheNode<K, V> node = caches.get(k);
        if (node == null) {
            return null;
        }
        //将当前node 移动到首位
        moveToFirst(node);
        return node.value;
    }

    public V remove(K k) {
        CacheNode<K, V> node = caches.get(k);
        if (node != null) {
            //获取当前删除节点的前置节点,如果不为空 把前置节点的下一节点 置为当前删除节点的下一节点  (链表重新链接)
            if (node.pre != null) {
                node.pre.next = node.next;
            }
            //获取当前删除节点的后置节点,如果不为空 把后置节点的前置节点 置位当前删除节点的上一节点(链表重新连接)
            if (node.next != null) {
                node.next.pre = node.pre;
            }
            //判断当前删除节点是否是首节点 是的话 首节点需要变更 需要变更为当前删除节点的下一节点
            if (node == first) {
                first = node.next;
            }
            //判断当前删除
            if (node == last) {
                last = node.pre;
            }
            currentCacheSize--;
        }
        return caches.remove(k).value;
    }

    public void clear() {
        first = null;
        last = null;
        currentCacheSize = 0;
        cacheCapacity = 0;
        caches.clear();
    }

    private void moveToFirst(CacheNode<K, V> node) {
        //判断当前节点是否是首节点 初始化的时候 first 是null
        if (first == node) {
            return;
        }
        //自行想一下链表的结构
        if (node.next != null) {
            node.next.pre = node.pre;
        }
        //自行想一下链表的结构
        if (node.pre != null) {
            node.pre.next = node.next;
        }
        //如果节点是尾节点,
        if (node == last) {
            last = last.pre;
        }
        //如果first 节点和last节点都是空的  代表是缓存的第一个数据 需要将该节点设置为首节点和尾节点
        if (first == null || last == null) {
            first = last = node;
            return;
        }
        node.next = first;
        first.pre = node;
        first = node;
        first.pre = null;
    }

    /**
     * 移除最后一个元素
     */
    private void removeLast() {
        //最后一个元素是否为空
        if (last != null) {
            last = last.pre;
            if (last == null) {
                first = null;
            } else {
                last.next = null;
            }
        }
        currentCacheSize--;
    }

    static class CacheNode<K, V> {
        CacheNode<K, V> pre;
        CacheNode<K, V> next;
        K key;
        V value;

        public CacheNode() {

        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值