LruCache 实现原理分析

LruCache 实现原理分析

最近研究了一下LruCahce的实现原理,以前也看过几遍源码了,但是有些还是没有理解清楚。重新撸了一遍代码,吼吼吼。

声明的变量
    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;
    private int maxSize;

    private int putCount;
    private int createCount;
    private int evictionCount;
    private int hitCount;
    private int missCount;
  • map:存放数据的集合
  • size:当前LruCahce的内存占用大小
  • maxSizeLrucache的最大容量
  • putCountput的次数
  • createCountcreate的次数
  • evictionCount:回收的次数
  • hitCount:命中的次数
  • missCount:丢失的次数
构造函数:
public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

这里设置了maxSize,以及实例化了一个LinkedHashMap对象,这个LinkedHashMap对象是实现Lru算法的关键,Lru是最近最少使用算法的简称,意思呢就是查询出最近的时间使用次数最少的那个对象。

new LinkedHashMap<K, V>(0, 0.75f, true)这句代码表示,初始容量为零,0.75是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。表示访问元素的排序方式,true表示按照访问顺序排序,false表示按照插入的顺序排序。

我这里写了一个小程序专门研究这两个参数的不同之处。

当设置为true的时候:

public static final void main(String[] args) {
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
        map.put(0, 0);
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.put(5, 5);
        map.put(6, 6);
        map.get(1);
        map.get(2);

        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());

        }
    }

输出结果是:

0:0
3:3
4:4
5:5
6:6
1:1
2:2

当设置为false的时候,输出顺序为:

0:0
1:1
2:2
3:3
4:4
5:5
6:6

有以上结果可以看出,这个设置为true的时候,如果对一个元素进行了操作(put、get),就会把那个元素放到集合的最后,设置为false的时候,无论怎么操作,集合元素的顺序都是按照插入的顺序来进行存储的。

到了这里我们可以知道,这个LinkedHashmap正是实现Lru算法的核心之处,当内容容量达到最大值的时候,只需要移除这个集合的前面的元素直到集合的容量足够存储数据的时候就可以了。

下面我们来看一看put方法:

 public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;   //put的次数+1
            size += safeSizeOf(key, value); //把当前容量增加,增加值为value的大小
            previous = map.put(key, value); //previous为旧的值
            if (previous != null) {
                size -= safeSizeOf(key, previous); //如果旧的值不为空,就把旧的值得大小减去
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value); 
        }

        trimToSize(maxSize);
        return previous;
    }

由上面的代码可以看出来,首先把size增加,然后判断是否以前已经有元素,如果有,就更新当前的size,并且调用entryRemoved方法,entryRemoved是一个空实现,如果我们使用LruCache的时候需要掌握元素移除的信息,可以重写这个方法。最后就会调用trimToSize,来调整集合中的内容。

trimToSize 的实现如下:

public 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 || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

这个方法是一个无限循环,跳出循环的条件是,size < maxSize或者 map 为空。主要的功能是判断当前容量时候已经超出最大的容量,如果超出了maxSize的话,就会循环移除map中的第一个元素,直到达到跳出循环的条件。由上面的分析知道,map中的第一个元素就是最近最少使用的那个元素。

研究完了put方法之后,下面开始研究get方法。

 public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;  //命中 + 1
                return mapValue;
            }
            missCount++;//丢失+1
        }

        V createdValue = create(key); //创建
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;  //创建 + 1
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                //如果有矛盾,意思就是有旧的值,就撤销put操作
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

这个方法就先通过key来获取value,如果能获取到就就直接返回,获取不到的话,就调用create()方法创建一个,事实上,如果我们不重写这个create方法的话是return null的,所以整个流程就是获取得到就直接返回,获取不到就返回null。至于后面那段代码呢?我看了几遍也没理解是适合什么场景的。反正就是重写的create方法之后就会执行后面的代码,不过我们通常使用的时候都是没有重写这个方法的。

最后说一下remove方法:

public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

remove方法没什么可以研究的了,就是使用remove方法移除一个元素。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值