LinkedHashMap和LruCache源码解析

本文深入剖析了LinkedHashMap的实现原理及其在LRU缓存中的应用。重点介绍了插入排序与访问排序机制,详细解释了put和get方法的工作流程,并通过源码分析展示了LRU缓存如何利用LinkedHashMap实现数据的高效管理和淘汰策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先了解一下LinkedHashMap两个排序:插入排序和访问排序

  • 插入排序和访问排序:队列方式,最新插入的放在尾部,最开始进来的放在头部;出栈出的是头部
  • 访问排序:最新的访问放在尾部,出栈出的是头部

LinkedHashMap源码分析

参数详情
 //头部节点
 transient LinkedEntry<K, V> header;
 //如果true代表访问排序,false为插入排序,默认为false
 private final boolean accessOrder;
LinkedEntry源码分析
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {//可以看出默认是继承与HashMapEntry
         //下一个节点
         LinkedEntry<K, V> nxt;
         //上一个节点
        LinkedEntry<K, V> prv;

}

获得最老的节点

 //获得最老的节点
      public Entry<K, V> eldest() {
        LinkedEntry<K, V> eldest = header.nxt;//头部指向的第一个参数
        return eldest != header ? eldest : null;//判断是否是空循环队列
    }

put源码解析

这里我们发现没有LinedHashMap的put方法,但是我可以知道LinedHashMap是继承与HashMap,所以我们看下HashMap的put方法之后我们可以知道,最终添加的方法是这个 addNewEntry(key, value, hash, index);所以LinedHashMap复写了该方法

@Override void addNewEntry(K key, V value, int hash, int index) {
        LinkedEntry<K, V> header = this.header;//获得当前的头节点
        //获得当前最老的节点
        LinkedEntry<K, V> eldest = header.nxt;
        //如果当前最老的节点不是头结点,removeEldestEntry(eldest)默认为false
        if (eldest != header && removeEldestEntry(eldest)) {
            remove(eldest.key);
        }
        //获得当前头节点目前的最后一个节点
        LinkedEntry<K, V> oldTail = header.prv;
        //创建一个新的节点
        LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
                key, value, hash, table[index], header, oldTail);
         //目前头部的前一个指针和之前的最后一个节点的下一个指针都指向新的节点
        table[index] = oldTail.nxt = header.prv = newTail;
    }

get源码解析

 @Override public V get(Object key) {
          if (key == null) {
            HashMapEntry<K, V> e = entryForNullKey;
            if (e == null)
                return null;
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                if (accessOrder)//如果是访问排序
                     //看这个
                    makeTail((LinkedEntry<K, V>) e);
                return e.value;//获得数据
            }
        }
        return null;
    }
    //把访问的数据放到尾部
    private void makeTail(LinkedEntry<K, V> e) {
        // 假设01,02,03三个节点,当前节点02
        //当前节点的上一个节点01的下一个指针=02的下一个指针指向的03
        e.prv.nxt = e.nxt;
        //当前节点的下一个节点03的前指针=02指向的前指针指向的01
        //这样就提取出e,将e的前后两个链接在一起
        e.nxt.prv = e.prv;
       //获得头部
        LinkedEntry<K, V> header = this.header;
        //获得头部最后一个
        LinkedEntry<K, V> oldTail = header.prv;
        e.nxt = header;
        e.prv = oldTail;
        oldTail.nxt = header.prv = e;
        modCount++;
    }
LruCache源码分析

分析之前我们回顾下LinkedHashMap源码分析,我们知道当accessOrder为true的时候是访问排序
直接看构造函数

 public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //第一个参数初始化容量,第二个参数代表0.75加载因子,第三个参数为true代表访问排序
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

get源码解析

public final V get(K key) {
        if (key == null) {//查询key不可为空
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);//获取当前值
            if (mapValue != null) {
                hitCount++;//成功次数+1
                return mapValue;
            }
            missCount++;//失败次数+1
        }
        //创建一个空值,这时候也就是说我们失败的时候需要重写create()方法
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
      //重写create方法后
        synchronized (this) {
            createCount++;//创建的次数+1
            //将自己创建的值添加到LinkedHashMap中,此时会调用LinkedHashMap的addNewEntry(key, value, hash, index);添加到尾部
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {
                //反复判断  防止比如插入图片因为时间问题导致已经插入但是未知的原因
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //条目删除,空方法
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
        //看这个
            trimToSize(maxSize);
            return createdValue;
        }
    }

trimToSize源码分析:当大于最大值时不断删减,直到和maxsize大小一样

 private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {//如果大小小于最大值直接返回
                    break;
                }
                Map.Entry<K, V> toEvict = null;
                //获取链表最后一条的key
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    //不断遍历,直到最后一条数据
                    toEvict = entry;
                }
                if (toEvict == null) {//如果最后一条数据的key为空,即大于最大大小的值是没有的
                    break;//直接返回
                }
                key = toEvict.getKey();//获取key
                value = toEvict.getValue();//获取value
                map.remove(key);//从集合中移除
                size -= safeSizeOf(key, value);//safeSizeOf返回的值是1
                evictionCount++;//删除的次数+1
            }
          //第一个参数如果是true代表是移除并腾出空间,false代表是由put或者remove引起的
            entryRemoved(true, key, value, null);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值