程序员子龙(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 - 附重点源码解析
      • 为什么使用LRU
      • LRU的LinkedHashMap实现
      • LinkedHashMap实现Map有序 - 源码分析
        • HashMap
        • LinkedHashMap
      • LinkedHashMap实现LRU - 源码分析
    • 去掉 if...else 的七种绝佳之法
    • 一眼看清@JSONField注解使用与效果
  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

  • Nginx

  • 面试

  • 生产问题

  • 系统设计

  • 消息中间件

  • Java
  • 基础
程序员子龙
2024-04-10
目录

LinkedHashMap实现LRU - 附重点源码解析

# 为什么使用LRU

有些数据需要缓存在内存中,以便高效查询。但是当缓存的数据量很大,并且某一时间段只有某小部分缓存数据被频繁使用(称之为热点数据),而其他缓存数据暂时没有访问,这时就需要LRU策略对热点数据进行保留,对非热点数据进行及时下线,保证缓存空间健康。 应用场景: 商城分时段商品秒杀

# LRU的LinkedHashMap实现

创建LRULinkedHashMap继承LinkedHashMap并重写removeEldestEntry方法,该方法返回的boolean代表是否删除最早使用/存放的Entry。

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

    private int threshold;

    public LRULinkedHashMap(int threshold) {
        super(16, 0.75f, true);
        this.threshold = threshold;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > threshold;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 使用
  1. get操作,会将节点移动到队尾。
  2. put操作,如果key不存在,则节点放置队尾,如果节点存在,会将节点移动到队尾。
public static void main(String[] args) {
    LRULinkedHashMap<String, String> lruLinkedHashMap = new LRULinkedHashMap<>(5);
    lruLinkedHashMap.put("1", null);
    lruLinkedHashMap.put("2", null);
    lruLinkedHashMap.put("3", null);
    lruLinkedHashMap.put("4", null);
    lruLinkedHashMap.put("5", null);
    lruLinkedHashMap.put("6", null);
    lruLinkedHashMap.put("7", null);
    System.out.println(lruLinkedHashMap);
    lruLinkedHashMap.get("4");
    System.out.println(lruLinkedHashMap);
    lruLinkedHashMap.put("5", null);
    System.out.println(lruLinkedHashMap);

    out => {3=null, 4=null, 5=null, 6=null, 7=null}
    out => {3=null, 5=null, 6=null, 7=null, 4=null}
    out => {3=null, 6=null, 7=null, 4=null, 5=null}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# LinkedHashMap实现Map有序 - 源码分析

LinkedHashMap继承自HashMap,HashMap采用数组加链表的结构存储数据,存储节点为HashMap.Node,分别存放hash值,key,value,以及指向下一个Node节点的next指针,链表结构是单项链表,HashMap并没有维护有序性。

# HashMap

img

LinkedHashMap继承了HashMap,也是采用了数据加链表的结构,不同的是LinkedHashMap的存储节点(Entry)继承自HashMap.Node,多维护了before和after指针,用来指向上一个和下一个节点,实现双向链表。这个双向链表就可以实现Map有序性(access-order:访问顺序/insertion-order插入顺序,默认是insertion-order)。

# LinkedHashMap

img

  • HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    ...
}
1
2
3
4
5
6
7
  • LinkedHashMap.Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    ...
}
1
2
3
4
  • HashMap - newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}
1
2
3
  • LinkedHashMap - newNode
// 重写了HashMap - newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p); // 添加节点到队尾
    return p;
}
1
2
3
4
5
6
7
  • HashMap - forEach 先遍历数组,再顺序遍历链表
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
    Node<K,V>[] tab;
    if (action == null)
        throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (Node<K,V> e : tab) { // 先遍历数据
            for (; e != null; e = e.next) //再书序遍历链表
                action.accept(e.key, e.value);
        }
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • LinkedHashMap - forEach 直接遍历双向链表,实现有序性。
// Map overrides
public void forEach(BiConsumer<? super K, ? super V> action) {
    if (action == null)
        throw new NullPointerException();
    int mc = modCount;
    for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) // 直接遍历
        action.accept(e.key, e.value);
    if (modCount != mc)
        throw new ConcurrentModificationException();
}
1
2
3
4
5
6
7
8
9
10

# LinkedHashMap实现LRU - 源码分析

下面是设置LinkedHashMap为访问顺序时的示意图。

img

  • LinkedHashMap - 构造器 accessOrder默认为false,即维护插入顺序,设置为true时,维护访问顺序。
* @param  accessOrder the ordering mode - {@code true} for access-order, {@code false} for insertion-order
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
1
2
3
4
5
6
7
public LinkedHashMap() {
    super();
    accessOrder = false; // 默认为插入顺序
}
1
2
3
4
  • LinkedHashMap - get 当设置为访问顺序时,在get/put的时候调用afterNodeAccess方法,将节点移动到队尾。
public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);// 将节点移动到队尾
    return e.value;
}
1
2
3
4
5
6
7
8
  • LinkedHashMap - put - 更新值 下面是HashMap的put操作,调用LinkedHashMap 的afterNodeAccess方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    ....
    if (e != null) { // existing mapping for key
        V oldValue = e.value;
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
        afterNodeAccess(e); // 将节点移动到队尾
        return oldValue;
    }
    ....
}
1
2
3
4
5
6
7
8
9
10
11
12
  • LinkedHashMap - put - 插入新值 插入新值, 调用linkNodeLast添加节点到队尾
// 重写了HashMap - newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p); // 添加节点到队尾
    return p;
}
1
2
3
4
5
6
7
上次更新: 2024/10/09, 22:27:53
Java 16进制字符串转10进制
去掉 if...else 的七种绝佳之法

← Java 16进制字符串转10进制 去掉 if...else 的七种绝佳之法→

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

    辽ICP备2023001503号-2

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