美文网首页
LruCache源码分析

LruCache源码分析

作者: 斌斌爱学习 | 来源:发表于2020-12-06 08:29 被阅读0次

对于LruCache的分析,我们可以从官方介绍开始,直接把源码中的注释拿过来。

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

简单翻译一下:首先用户缓存,它持有有限数量的value的强引用。每当某个value被取用时,他都会被移到队头。当某个value要被加入到一个已经满了的cache当中时,位于队尾的value将会被移除,并且能够被gc给回收到。

要分析一个类,我们主要看他的属性和方法,当然还有它所继承的接口和父类。
LruCache没用继承任何父类也没有实现任何接口,算是比较简单的一个类了。
那我们先来看它有哪些属性:

    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;

1.LinkedHashMap

其中最重要的也是LruCache的核心就是这个LinkedHashMap,如果对数据结构比较熟悉的同学应该知道它的底层逻辑了。这里简单说明一下:
LinkedHashMap继承自HashMap,所以他的存储数据的方式首先是<k,v>的方式,也就是键值对的方式。那它与HashMap有哪些不同点呢,首先就体现在Linked这个词上,Linked指的是LinkedList,也就是说它是有序的,而我们知道hashmap是无序的。也就是说,通过引入LinkedList使得LinkedHashMap可以排序。熟悉LinkedHashMap应该知道它有两种排序方式,一种是插入排序,一种是读取排序。LruCache正是巧妙的运用的LinkedHashMap的读取排序,来实现我们说的Lru算法(最近最少使用算法)。

2.size和maxSize
这两个单位要统一,比如我们常用LruCache来做图片内存缓存,图片的文件大小1M可以用来表示size单元。maxSize则表示最大的size。比如我们LruCache最多缓存50M,那么MaxSize就是50,而size则是value.getByteCount()/1024.
具体实现:

        long maxMemory = Runtime.getRuntime().maxMemory();
        int cacheSize = (int) (maxMemory / 8);//可用内存的八分之一
        LruCache  lruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

3.下面那几个count用得不多,所以这里也就不多做介绍。

下面我们再看看几个重要方法:

  1. 构造函数。
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);
    }

构造函数做了两件事,

  1. 定义了最大存储size,也就是我们上面分析的maxSize,是size的整数倍。
  2. 初始化了核心的LinkedHashMap,而且是用了三个参数的构造函数,第三个参数为true,也就是使用了读取排序。
    可以瞄一眼LinkedHashMap的构造函数,第三个用于表示是否要使用accessOrder。true是读取排序,而false的话则会采用插入排序。
 /**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
  1. resize,重新给定maxSize
public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

因为重新给定的maxSize可能要小于之前的size,所以就需要判断是否需要移除末尾的某些value来使得满足条件。

  1. trimToSize,移除最近最少使用的元素以致size小于maxSize。
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) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

当某个value被移除时,会调用entryRemoved

  1. entryRemoved 方法,如果你cache的某个值需要明确释放,重写entryRemoved()方法。这个方法会在元素被put或remove时调用,源码默认是空实现的。
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
  1. 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++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        trimToSize(maxSize);
        return previous;
    }

这里需要注意的是,LruCache不允许key或者value为空。当添加某个value进缓存实际上就是将其添加的map容器中,如果之前存在这个key对应的value,那么还会调用以下方法:
1.减少size 因为之前的value被移除而且能被垃圾回收了
2.调用entryRemoved方法
3.trimToSize 因为可能size超过了maxSize
4.返回previous

  1. 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++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

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

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

            if (mapValue != null) {
                // There was a conflict so undo that last 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;
        }
    }

正常情况下,其实就是从map中取key对应的value。还有一种情况,如果我们重写了create方法,那么情况就不一样了。
会将create的值先存入map,然后判断是否之前key已经对应有值了,有的话再将之前的值替换当前新创建的值并调用entryRemoved函数,没有的话则需要重新赋值size并调用trimToSize。

其他的方法就不太常用了,在这里就不讨论了。

讲到这里,就不得不提一下,如果让你实现一个图片加载框架的话,你会怎么实现呢?

可以后续分析以下glide的源码

相关文章

  • Android-Glide源码解析

    一、LruCache 要分析Glide的源码,首先就需要分析LruCache。LruCache是基于LinkedH...

  • LruCache之LruCache分析

    LruCache 分析 LruCache 是 Android 的一个内部类,提供了基于内存实现的缓存 用法 源码 ...

  • LruCache

    文章主要介绍了:1.LruCache的基本使用2.LruCache的源码分析3.基于LinkedHashMap的实...

  • LruCache源码分析

    LruCache类里面有一个LinkedHashMap map的变量,缓存主要就是用这个map来做的,l...

  • 源码分析 LruCache

    简介 为什么用 一个app持有的内存是有限的,无限制的使用强引用在内存中缓存数据,有可能导致OOM。 能做什么 L...

  • LruCache源码分析

    在开发中我们会经常碰到一些资源需要做缓存优化,例如Bitmap,Json等,那么今天我们来瞧瞧默默无闻的LruCa...

  • LruCache源码分析

    LruCache的原理 LruCache主要靠LinkedHashMap的一个按访问排序的特性实现的,Linked...

  • LruCache源码分析

    LruCache的源码分析已经很多了,看了很多遍,但是自己走一遍分析,才是真正的掌握,将知识转化到自身。 用途 L...

  • LruCache源码分析

    LruCache的源码分析已经很多了,看了很多遍,但是自己走一遍分析,才是真正的掌握,将知识转化到自身。 用途 L...

  • LruCache源码分析

    对于LruCache的分析,我们可以从官方介绍开始,直接把源码中的注释拿过来。 A cache that hold...

网友评论

      本文标题:LruCache源码分析

      本文链接:https://www.haomeiwen.com/subject/ybonwktx.html