--算法--用HashMap简单实现LFU缓存算法(Java实现)

15 篇文章 0 订阅
7 篇文章 0 订阅

■ 什么是LFU?

LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,也可以说是最近最少使用,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。 —来源: 百度百科

通俗点说就是有一个缓存,最多存三个数,现在已经有:a,b,c 三个数,然后他们分别被点击的次数为:3,1,2,然后现在有个新的数 d 要存入缓存中,这时因为缓存满了,要剔除掉一个数,根据LFU算法,会剔除掉最近最少使用的数,即字母 a,a剔除后,将 d 读入缓存,如果此时在没有点击任何数的情况下,又有一个数要存入缓存,则会将 d 给剔除。

■ 算法描述与实现?

设计一种缓存结构,该结构在构造时确定大小,假设大小为 K,并有两个功能:

set(key,value):将记录(key,value)插入该结构。当缓存满时,将访问频率最低的数据置换掉(就是删除掉)
get(key):返回key对应的value值。

实现:创建一个类用来存放对应的key值和命中次数,并且实现comparable类,使其能按照命中次数从小到大排序,接着用hashMap来存放对应的健值对,在缓存满的时候,通过删除命中次数最少的元素将新元素加入缓存中。

■ 代码实现?

public class LFU<K, V> extends HashMap<K, V> {

    private static final int DEFAULT_MAX_SIZE = 5;
    private int max_size = DEFAULT_MAX_SIZE;
    private Map<K, HitRate> hashMap = new HashMap<>();  //变量类型是Map或HashMap都可以好像

    public static void main(String[] args) {
        LFU<String, String> cache = new LFU<>();
        cache.put("a", "1");
        cache.put("b", "2");
        cache.put("c", "3");
        cache.put("d", "4");
        cache.put("e", "5");
        //此时cache已经满,未定义最大容量,所以默认为5
        System.out.println("当前缓存:" + cache.toString());

        //此时a的命中次数为2
        cache.get("a");
        cache.get("a");

        //删除命中次数最少的b,按照先进先出的意思,找到b为命中次数最少的
        cache.put("f", "6");
        System.out.println("放入f后的缓存:" + cache.toString());

        cache.get("f");   //f命中+1
        cache.get("c");   //c命中+1
        cache.get("d");
        cache.get("e");
        cache.put("g", "7");
        System.out.println("放入g后的缓存:" + cache.toString());
        cache.put("h","8");  //删除命中最少的g
        System.out.println("放入h后的缓存:" + cache.toString());
    }


    //空构造器,使用默认最大值容量
    public LFU() {
        this(DEFAULT_MAX_SIZE);
    }

    //按照传入容量定义容量的构造器
    public LFU(int max_size) {
        super(max_size);
        this.max_size = max_size;
    }

    @Override
    public V get(Object key) {
        //返回指定键映射到的值  第一次get获得hash后对应的数组索引上
        V v = super.get(key);   //根据传入的key调用父级HashMap里的get将其hash()来获取对应的值
        if (v != null) {
            //第二次get取得当前数组索引位置上的HitRate结构上的元素 。并进行修改命中次数
            HitRate hitRate = hashMap.get(key);    //调用Map里的get(),拿到对应的HitRate结构
            hitRate.hitCount += 1;  //让命中次数加一
            System.out.println(key + "被命中" + hitRate.hitCount + "次");
            hitRate.atime = System.nanoTime();   //记录此次时间,纳秒为单位
        }
        //将获取到的值返回
        return v;
    }

    @Override
    public V put(K key, V value) {
        //如果缓存满了,即缓存大小已经到达或超过最大时了
        while (hashMap.size() >= max_size) {
            K k = getLFUAge();    //取出命中最少的键
            hashMap.remove(k);
            this.remove(k);   //从该映射中移除指定键的映射(如果存在)
        }
        //对于数据插入,这里要进行两次Hash去定位数据的存储位置
        V v = super.put(key, value);
        hashMap.put(key, new HitRate(key, 0, System.nanoTime()));
           //增添键值信息
        return v;
    }

    //获取缓存中被命中次数最少的
    private K getLFUAge() {
        //values()返回此映射中包含的值的集合视图
        /***
         * 根据给定集合元素的自然顺序返回该集合的最小元素。*集合中的所有元素都必须实现Comparable接口
         */
        HitRate min = Collections.min(hashMap.values());
        return min.key;
    }

    class HitRate implements Comparable<HitRate> {

        private K key;
        private Integer hitCount;   //记录命中次数
        private Long atime;  //上次命中时间

        public HitRate(K key, Integer hitCount, long atime) {
            this.key = key;
            this.hitCount = hitCount;
            this.atime = atime;
        }

        @Override
        public String toString() {
            return "HitRate{" +
                    "key=" + key +
                    ", hitCount=" + hitCount +
                    ", atime=" + atime +
                    '}';
        }

        @Override
        public int compareTo(HitRate o) {
            int byHitCount = hitCount.compareTo(o.hitCount);   //用命中次数来从小到大排序 相等则=0
            return byHitCount != 0 ? byHitCount : atime.compareTo(atime);   //如果没被命中且要排序就用上次命中时间排序
        }
    }
}

打印结果:

当前缓存:{a=1, b=2, c=3, d=4, e=5}
a被命中1次
a被命中2次
放入f后的缓存:{a=1, c=3, d=4, e=5, f=6}
f被命中1次
c被命中1次
d被命中1次
e被命中1次
放入g后的缓存:{a=1, d=4, e=5, f=6, g=7}
放入h后的缓存:{h=8, a=1, d=4, e=5, f=6}

♦ 总结

LFU也有自己的瑕疵的,比如在一个缓存页数总共为3的网页上,你从网页A->网页B->网页A->网页B->网页C->网页D,注意浏览网页C时,此时缓存已经满了,当你浏览网页D时,会将网页C即网页D的前一页剔除,此时你再返回上一页,网页的内容已经被清除,需要重新缓存,而不是像LRU那样,将最近最久没用的网页A删除,当然有利有弊,还需要自己斟酌使用哪一个,想了解LRF的实现可以参考下这篇文章:网页链接
❤ 加油

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值