LFU算法代码实现记录一下

15 篇文章 0 订阅

前言

  • 之前有写过一篇关于LRU淘汰算法的文章 LRU算法记录一下,lru算法思想是淘汰最近最少使用的元素,当一个元素在一段时间内没有访问过后,那么在之后的一段时间也极有可能不会被访问,然后当数据池满了后将其淘汰掉。
  • 这篇文章打算写一下关于LFU算法思想和实现,lfu算法思想是淘汰掉最近时间使用频率最低的元素,如果有多个元素的的使用频率都是最低的,那么就淘汰掉在数据池里存在时间最短的元素。
  • LRU算法和LFU算法相比,LRU是基于元素使用时间维度去淘汰数据,而LFU是基于使用频率维度去淘汰数据。一般来说lfu淘汰算法要优于lru淘汰算法,只要更适合业务场景就行。
  • 之前有点懒所以就没有写lru算法的代码实现-_-。这次先把lfu算法的代码实现先弄出来,后面有机会找个时间再把lru算法的代码实现写出来吧,反正当了解其思想原理后实现起来其实都挺简单。

LFU代码实现之O(N)实现

  • 首先根据LFU算法思想直接想到的是链表实现:

/**
 * LFU算法链表实现
 *
 * @author zrh
 * @date 2021/03/28
 */
public class LfuTest {

    /**
     * 链表数据池容量大小
     */
    private final static Integer size = 3;

    public static void main(String[] args) {
        // 模拟插入的元素
        String[] data = {"a", "b", "c", "a", "a", "a", "a", "b", "c", "d"};
        LinkedList list = new LinkedList<LfuNode>();
        for (String param : data) {
            // 执行插入操作
            test(list, param);
            System.out.println("添加元素【" + param + "】,结果:" + list);
        }
    }

    private static void test(LinkedList<LfuNode> list, String param) {
        // 先判断链表内是否有元素
        if (!list.isEmpty()) {
            Iterator<LfuNode> iterator = list.iterator();
            while (iterator.hasNext()) {
                LfuNode next = iterator.next();
                // 判断链表内元素有当前插入的元素
                if (next.value.equals(param)) {
                    // 数据使用频率+1,并移除数据
                    next.fre += 1;
                    iterator.remove();
                    // 循环数据比较fre,在适合的位置重新插入数据到链表
                    for (int i = 0; i < list.size(); i++) {
                        if (i == list.size() - 1) {
                            // 已经到最后位了,就直接添加到链表尾部
                            list.addLast(next);
                            return;
                        } else if (list.get(i).fre <= next.fre
                                && list.get(i + 1).fre > next.fre) {
                            // i位元素数据的频率小于等于当前next的频率,并且当前next的频率小于i+1位元素数据的频率,则在i+1处插入next.
                            // 这里使用 && 是如果元素相同的频率就把元素尽量放在最右边,因为删除元素时是直接删除最左元素(链表头元素)。
                            list.add(i + 1, next);
                            return;
                        }
                    }
                    return;
                }
            }
            // 如果链表内有数据,并且插入新数据,并且链表大小已经达到最大,则直接删除链表头节点元素。
            if (list.size() == size) {
                list.removeFirst();
            }
        }
        // 如果链表没有元素,那直接在链表头结点添加元素
        list.addFirst(new LfuNode(param));
    }

    @Data
    static class LfuNode {
        // 数据
        private String value;
        // 数据使用频率
        private Integer fre;

        public LfuNode(String value) {
            this.value = value;
            this.fre = 1;
        }
    }
}
---------------------------------------
运行后打印结果:
添加元素【a】,结果:[LfuTest.LfuNode(value=a, fre=1)]
添加元素【b】,结果:[LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=1)]
添加元素【c】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=1)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=2)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=3)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=4)]
添加元素【a】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=1), LfuTest.LfuNode(value=a, fre=5)]
添加元素【b】,结果:[LfuTest.LfuNode(value=c, fre=1), LfuTest.LfuNode(value=b, fre=2), LfuTest.LfuNode(value=a, fre=5)]
添加元素【c】,结果:[LfuTest.LfuNode(value=b, fre=2), LfuTest.LfuNode(value=c, fre=2), LfuTest.LfuNode(value=a, fre=5)]
添加元素【d】,结果:[LfuTest.LfuNode(value=d, fre=1), LfuTest.LfuNode(value=c, fre=2), LfuTest.LfuNode(value=a, fre=5)]
  • 这里最后数据池(链表)内剩下的三个元素时【d,c,a】。而这里如果是使用lru算法淘汰数据,根据元素插入顺序后的结果是【b,c,d】。

LFU代码实现之O(1)实现

  • 利用hashmap的快速查找和LinkedHashSet的快速添加删除元素,使其时间复杂度优化为O(1)。
/**
 * LFU算法实现二:优化实现
 *
 * @Author: ZRH
 * @Date: 2021/3/29
 */
public class LfuCache<K, V> {

    /**
     * 数据池容量大小,最小使用频率
     */
    private static Integer capacity;
    private static Integer minFre;
    private Map<K, LfuNode<K, V>> map1;
    private Map<Integer, LinkedHashSet<LfuNode<K, V>>> map2;

    /**
     * 构造方法 - 初始化参数
     *
     * @param capacity
     */
    public LfuCache (Integer capacity) {
        this.capacity = capacity;
        this.minFre = 1;
        this.map1 = new HashMap<>();
        this.map2 = new HashMap<>();
    }

    /**
     * 添加元素方法
     *
     * @param k
     * @param v
     */
    private void add (K k, V v) {
        // 添加第一个元素
        if (map1.isEmpty()) {
            LfuNode node = new LfuNode(k, v);
            map1.put(k, node);
            LinkedHashSet set = new LinkedHashSet();
            set.add(node);
            map2.put(node.fre, set);
            return;
        }

        LfuNode<K, V> node;
        // 当前key存在数据缓存池内
        if ((node = map1.get(k)) != null) {
            // 移除节点使用频率为key的集合内的节点
            LinkedHashSet<LfuNode<K, V>> set1 = map2.get(node.fre);
            set1.remove(node);
            if (set1.isEmpty()) {
                // 清除为空的集合
                map2.remove(node.fre);
            }
            // 使用频率+1
            node.fre += 1;
            LinkedHashSet<LfuNode<K, V>> set2 = map2.get(node.fre);
            if (null == set2) {
                set2 = new LinkedHashSet();
            }
            // 把当前节点加入到新的使用频率为key的集合中
            set2.add(node);
            map2.put(node.fre, set2);
            // 维护最小使用频率参数
            minFre = map2.keySet().stream().min(Integer::compare).get();
            return;
        }
        // 根据K V创建新的节点对象
        node = new LfuNode(k, v);
        // 当前数据池容量大小已经满了,淘汰使用频率最小的元素。
        // 如果集合内使用频率最小的有多个元素,那淘汰存在最久的元素。
        if (map1.size() == capacity) {
            // 根据最小使用频率为key取出集合
            LinkedHashSet<LfuNode<K, V>> set = map2.get(minFre);
            // 根据LinkedHashSet集合插入有序性,删除第一个元素(第一个元素表示存在最久的元素)
            final Iterator<LfuNode<K, V>> iterator = set.iterator();
            LfuNode<K, V> oldNode = null;
            while (iterator.hasNext()) {
                oldNode = iterator.next();
                iterator.remove();
                break;
            }
            // 淘汰map1内元素
            map1.remove(oldNode.key);
        }
        // 插入新元素到map1,map2集合中
        map1.put(k, node);
        LinkedHashSet<LfuNode<K, V>> set1 = map2.get(node.fre);
        if (set1 == null) {
            set1 = new LinkedHashSet();
        }
        set1.add(node);
        map2.put(node.fre, set1);
        // 维护最小使用频率参数
        minFre = node.fre;
        return;
    }

    @Data
    static class LfuNode<K, V> {
        /**
         * K键 V值 fre使用频率
         */
        private K key;
        private V value;
        private Integer fre;

        public LfuNode (K key, V value) {
            this.key = key;
            this.value = value;
            this.fre = 1;
        }
    }

    public static void main (String[] args) {
	    String[] data = {"a", "b", "c", "a", "a", "a", "a","b", "c", "d"};
        LfuCache cache = new LfuCache(3);
        for (String param : data) {
            cache.add(param, param);
            System.out.println("-------------------------");
            System.out.println("插入元素【" + param + "】,元素最小频率=" + minFre);
            System.out.println(" map1 = " + JSON.toJSONString(cache.map1));
            System.out.println(" map2 = " + JSON.toJSONString(cache.map2));
        }
    }
}
运行后打印结果:
-------------------------
插入元素【a】,元素最小频率=1
 map1 = {"a":{"fre":1,"key":"a","value":"a"}}
 map2 = {1:[{"fre":1,"key":"a","value":"a"}]}
-------------------------
插入元素【b】,元素最小频率=1
 map1 = {"a":{"fre":1,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"}}
 map2 = {1:[{"fre":1,"key":"a","value":"a"},{"fre":1,"key":"b","value":"b"}]}
-------------------------
插入元素【c】,元素最小频率=1
 map1 = {"a":{"fre":1,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"a","value":"a"},{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}]}
-------------------------
插入元素【a】,元素最小频率=1
 map1 = {"a":{"fre":2,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],2:[{"fre":2,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
 map1 = {"a":{"fre":3,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],3:[{"fre":3,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
 map1 = {"a":{"fre":4,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],4:[{"fre":4,"key":"a","value":"a"}]}
-------------------------
插入元素【a】,元素最小频率=1
 map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":1,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"b","value":"b"},{"fre":1,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【b】,元素最小频率=1
 map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":2,"key":"b","value":"b"},"c":{"fre":1,"key":"c","value":"c"}}
 map2 = {1:[{"fre":1,"key":"c","value":"c"}],2:[{"fre":2,"key":"b","value":"b"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【c】,元素最小频率=2
 map1 = {"a":{"fre":5,"key":"a","value":"a"},"b":{"fre":2,"key":"b","value":"b"},"c":{"fre":2,"key":"c","value":"c"}}
 map2 = {2:[{"fre":2,"key":"b","value":"b"},{"fre":2,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}
-------------------------
插入元素【d】,元素最小频率=1
 map1 = {"a":{"fre":5,"key":"a","value":"a"},"c":{"fre":2,"key":"c","value":"c"},"d":{"fre":1,"key":"d","value":"d"}}
 map2 = {1:[{"fre":1,"key":"d","value":"d"}],2:[{"fre":2,"key":"c","value":"c"}],5:[{"fre":5,"key":"a","value":"a"}]}

最后

  • 在Redis的内存淘汰策略和dubbo缓存淘汰中都有支持lfu算法。
  • 上述代码如果有什么错误欢迎指出,我会加以改正 -_-
  • 参考 LFU算法详解
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值