程序员子龙(Java面试 + Java学习) 程序员子龙(Java面试 + Java学习)
首页
学习指南
工具
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
开源项目
技术书籍
  • 基础

    • java 学习路线图
    • HashMap 详解
    • Java 8 日期和时间 - 如何获取当前时间和时间戳?
    • Java 模板变量替换(字符串、占位符替换)
    • JDK 代理
    • Java SPI 详解
    • java stream 看这一篇文章就够了
    • Java 泛型详解
    • Java 动态修改注解值
    • 如何正确遍历删除List中的元素
    • 什么是Java注解
    • 异步编程神器:CompletableFuture详解
    • FIFO 、LRU、LFU算法
      • 十进制转十六进制
      • java中double类型数据加减乘除操作精度丢失问题及解决方法
      • JAVA利用反射清除实体类对应字段
      • JSON转换问题最全详解(json转List,json转对象,json转JSONObject)
      • java 8 List 根据某个字段去重
      • Java List排序
      • 压缩算法:字符串(JSON)压缩和解压【JDK之Deflater压缩与Inflater解压】
      • BCD码是什么?
      • Java二进制、八进制、十进制、十六进制转换
      • Java String字符串 与 ASCII码相互转换
      • 什么是跨域?解决方案有哪些?
      • Java 16进制字符串转10进制
      • LinkedHashMap实现LRU - 附重点源码解析
      • 去掉 if...else 的七种绝佳之法
      • 一眼看清@JSONField注解使用与效果
    • JVM

    • Spring

    • 并发编程

    • Mybatis

    • 网络编程

    • 数据库

    • 缓存

    • 设计模式

    • 分布式

    • 高并发

    • SpringBoot

    • SpringCloudAlibaba

    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • 基础
    程序员子龙
    2024-01-29
    目录

    FIFO 、LRU、LFU算法

    # 简介

    三种常见的缓存策略:

    FIFO:First In First Out,先进先出

    LRU:Least Recently Used,最近最少使用

    LFU:Least Frequently Used,最不经常使用

    # FIFO

    FIFO 按照“先进先出(First In,First Out)” 的原理淘汰数据,最先加载到内存的最先被置换出去,符合队列的特性,数据结构上使用队列Queue来实现。

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

    1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最先进入缓存的数据置换掉。
    2. get(key):返回key对应的value值。

    新数据插入 FIFO队列尾部,数据在FIFO队列中顺序移动;

    淘汰FIFO队列头部的数据;

    # LRU

    最近最久未使用(Least Recently Used LRU)算法是⼀种缓存淘汰策略,它是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。该算法的思路是,发生缺页中断时,将最近一段时间内最久未使用的页面置换出去。 从程序运行的原理来看,最近最久未使用算法是比较接近理想的一种页面置换算法,这种算法既充分利用了内存中页面调用的历史信息,又正确反映了程序的局部问题。

    LRU算法的思想是:如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。

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

    1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最久未使用的数据置换掉。
    2. get(key):返回key对应的value值。

    # java 实现 LRU 算法

    用LinkedHashMap来实现的LRU算法的缓存。

    public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    
        private int cacheSize;
        public LRUCache(int cacheSize) {
            super(16, (float) 0.75, true);
            this.cacheSize = cacheSize;
        }
    
    
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > cacheSize;
        }
    }
    
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Test {
    
    
        private static LRUCache<String, Integer> cache = new LRUCache<>(10);
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 10; i++) {
                cache.put("key" + i, i);
            }
            System.out.println("all cache :" + cache);
            cache.get("key3");
            System.out.println("get key3 :"+ cache);
            cache.get("key4");
            System.out.println("get key4: "+ cache);
            cache.get("key4");
            System.out.println("get key4 :"+ cache);
            cache.put("key" + 10, 10);
            System.out.println("cache :"+ cache);
            cache.put("key" + 11, 11);
            System.out.println("cache :"+ cache);
            cache.put("key" + 12, 12);
            System.out.println("cache :"+ cache);
    
        }
    
    
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    输出结果:

    all cache :{key0=0, key1=1, key2=2, key3=3, key4=4, key5=5, key6=6, key7=7, key8=8, key9=9} get key3 :{key0=0, key1=1, key2=2, key4=4, key5=5, key6=6, key7=7, key8=8, key9=9, key3=3} get key4: {key0=0, key1=1, key2=2, key5=5, key6=6, key7=7, key8=8, key9=9, key3=3, key4=4} get key4 :{key0=0, key1=1, key2=2, key5=5, key6=6, key7=7, key8=8, key9=9, key3=3, key4=4} cache :{key1=1, key2=2, key5=5, key6=6, key7=7, key8=8, key9=9, key3=3, key4=4, key10=10} cache :{key2=2, key5=5, key6=6, key7=7, key8=8, key9=9, key3=3, key4=4, key10=10, key11=11} cache :{key5=5, key6=6, key7=7, key8=8, key9=9, key3=3, key4=4, key10=10, key11=11, key12=12}

    # LFU

    LFU(Least Frequently Used),最近最少使用策略,也就是说在一段时间内,数据被使用频次最少的,优先被淘汰。

    LFU将数据和数据的访问频次保存在一个容量有限的容器中,当访问一个数据时:

    1. 该数据在容器中,则将该数据的访问频次加1。
    2. 该数据不在容器中,则将该数据加入到容器中,且访问频次为1。

    当数据量达到容器的限制后,会剔除掉访问频次最低的数据。下图是一个简易的LFU算法示意图。

    # LFU实现

    可以使用双哈希表进行实现,一个哈希表用于存储对应的数据,另一个哈希表用于存储数据被使用次数和对应的数据。

    package com.demo.web.java;
    
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public class LFUCache<K, V> {
    
        /**
         * 容量限制
         */
        private int capacity;
    
        /**
         * 当前最小使用次数
         */
        private int minUsedCount;
    
        /**
         * key和数据的映射
         */
        private Map<K, Node> map;
        /**
         * 数据频率和对应数据组成的链表
         */
        private Map<Integer, List<Node>> usedCountMap;
    
        public LFUCache(int capacity) {
            this.capacity = capacity;
            this.minUsedCount = 1;
            this.map = new HashMap<>();
            this.usedCountMap = new HashMap<>();
        }
    
        public V get(K key) {
    
            Node node = map.get(key);
            if (node == null) {
                return null;
            }
            // 增加数据的访问频率
            addUsedCount(node);
            return node.value;
        }
    
        public V put(K key, V value) {
            Node node = map.get(key);
            if (node != null) {
                // 如果存在则增加该数据的访问频次
                V oldValue = node.value;
                node.value = value;
                addUsedCount(node);
                return oldValue;
            } else {
                // 数据不存在,判断链表是否满
                if (map.size() == capacity) {
                    // 如果满,则删除队首节点,更新哈希表
                    List<Node> list = usedCountMap.get(minUsedCount);
                    Node delNode = list.get(0);
                    list.remove(delNode);
                    map.remove(delNode.key);
                }
                // 新增数据并放到数据频率为1的数据链表中
                Node newNode = new Node(key, value);
                map.put(key, newNode);
                List<Node> list = usedCountMap.get(1);
                if (list == null) {
                    list = new LinkedList<>();
                    usedCountMap.put(1, list);
                }
    
                list.add(newNode);
                minUsedCount = 1;
                return null;
            }
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("LfuCache{");
            List<Integer> usedCountList = this.usedCountMap.keySet().stream().collect(Collectors.toList());
            usedCountList.sort(Comparator.comparingInt(i -> i));
            int count = 0;
            for (int usedCount : usedCountList) {
                List<Node> list = this.usedCountMap.get(usedCount);
                if (list == null) {
                    continue;
                }
                for (Node node : list) {
                    if (count > 0) {
                        sb.append(',').append(' ');
                    }
                    sb.append(node.key);
                    sb.append('=');
                    sb.append(node.value);
                    sb.append("(UsedCount:");
                    sb.append(node.usedCount);
                    sb.append(')');
                    count++;
                }
            }
            return sb.append('}').toString();
        }
    
        private void addUsedCount(Node node) {
            List<Node> oldList = usedCountMap.get(node.usedCount);
            oldList.remove(node);
    
            // 更新最小数据频率
            if (minUsedCount == node.usedCount && oldList.isEmpty()) {
                minUsedCount++;
            }
    
            node.usedCount++;
            List<Node> set = usedCountMap.get(node.usedCount);
            if (set == null) {
                set = new LinkedList<>();
                usedCountMap.put(node.usedCount, set);
            }
            set.add(node);
        }
    
        class Node {
    
            K key;
            V value;
            int usedCount = 1;
    
            Node(K key, V value) {
                this.key = key;
                this.value = value;
            }
        }
    
    
        public static void main(String[] args) {
            LFUCache<String, String> cache = new LFUCache(3);
            cache.put("keyA", "valueA");
            System.out.println("put keyA");
            System.out.println(cache);
            System.out.println("=========================");
    
            cache.put("keyB", "valueB");
            System.out.println("put keyB");
            System.out.println(cache);
            System.out.println("=========================");
    
            cache.put("keyC", "valueC");
            System.out.println("put keyC");
            System.out.println(cache);
            System.out.println("=========================");
    
            cache.get("keyA");
            System.out.println("get keyA");
            System.out.println(cache);
            System.out.println("=========================");
    
            cache.put("keyD", "valueD");
            System.out.println("put keyD");
            System.out.println(cache);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166

    运行结果:

    put keyA

    LfuCache{keyA=valueA(UsedCount:1)}

    put keyB

    LfuCache{keyA=valueA(UsedCount:1), keyB=valueB(UsedCount:1)}

    put keyC

    LfuCache{keyA=valueA(UsedCount:1), keyB=valueB(UsedCount:1), keyC=valueC(UsedCount:1)}

    get keyA

    LfuCache{keyB=valueB(UsedCount:1), keyC=valueC(UsedCount:1), keyA=valueA(UsedCount:2)}

    put keyD LfuCache{keyC=valueC(UsedCount:1), keyD=valueD(UsedCount:1), keyA=valueA(UsedCount:2)}

    # LFU相比于LRU的优劣

    区别:

    LFU是基于访问频次的模式,而LRU是基于访问时间的模式。

    优势:

    在数据访问符合正态分布时,相比于LRU算法,LFU算法的缓存命中率会高一些。

    劣势:

    1. LFU的复杂度要比LRU更高一些。
    2. 需要维护数据的访问频次,每次访问都需要更新。
    3. 早期的数据相比于后期的数据更容易被缓存下来,导致后期的数据很难被缓存。
    4. 新加入缓存的数据很容易被剔除,像是缓存的末端发生“抖动”。
    上次更新: 2024/01/30, 15:08:57
    异步编程神器:CompletableFuture详解
    十进制转十六进制

    ← 异步编程神器:CompletableFuture详解 十进制转十六进制→

    最近更新
    01
    一个注解,优雅的实现接口幂等性
    11-17
    02
    MySQL事务(超详细!!!)
    10-14
    03
    阿里二面:Kafka中如何保证消息的顺序性?这周被问到两次了
    10-09
    更多文章>
    Theme by Vdoing | Copyright © 2024-2024

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式