LruCache的终极解析

LruCache是android提供的一个常用而且有用的数据缓存工具类,通其中是通过键值对来保存数据.对于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.

其大意是:LruCache是一个通过强引用来保存一定数量的数据的缓存技术.每次使用一个数据时候,这个数据就会被移动到队列的最前面(这样维护来一个访问数据的顺序).当新加一个数据到这个已经满了的缓存时候,这个缓存里面最后面的数据(最不常使用的数据)就会被清理掉.

通过上面的说明,基本可以知道LruCache作用和实现方法,不过仅仅上面的说明要深刻理解其中奥秘还是优点困难.上面有四处标记红色的地方,下面通过对这四处红色文字做以下说明.

(1)"一定数量的数据": 这里的"一定数量"通常是指LruCache缓存数据的数量(个数),也就是说LruCache缓存的键值对数据的个数.注意,这里我说的是"通常".当然也有其他情况:也可以是缓存数据所占用的内存的size(其单位一般时k,当然byte都可以),这个时候,你就需要重写LruCache的这个方法:sizeOf(K
key, V value)来计算每一个键值对数据所占用的内存:

    private static LruCache<Integer, Drawable> createIconCache() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;
        return new LruCache<Integer, Drawable>(cacheSize) {
            protected int sizeOf(Integer key, Drawable drawable) {
                if (drawable instanceof BitmapDrawable) {//计算一个这个BitmapDrawable占用内存
                    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
                    return bitmap.getByteCount() / 1024;
                }
                return 1;//否则返回1,默认情况下返回的就是1
            }
        };
    }

上面的代码时候创建一个缓存Drawable的图片数据的LruCache对象,在方法sizeOf里面判断,如果当前缓存的是BitmapDrawable类型的图片数据,则返回的就是这个图片的内存大小,否则返回1.

很明显,当sizeOf方法返回1的时候,就表示当前的LruCache缓存的数据数量是指键值对数量.下面来看看LruCache中sizeOf方法默认是如何定义的:

protected int sizeOf(K key, V value) {
        return 1;
    }

默认情况下就是返回1的.所以如果你的LruCache缓存数据如果时图片并且/或者对缓存的数据的内存大小有限制的时候,就需要重写这个方法sizeOf(K key, V value)

(2)这个数据就会被移动到队列的最前面(这样维护来一个访问数据的顺序):其实很快可以知道,hashmap中的LinkedHashMap似乎就可以实现这个功能,LruCache当然也就使用来LinkedHashMap,来看看LruCache的构造方法就清楚了:

    public LruCache(int maxSize) {//这里maxSize是指当前LruCache缓存数据最大量
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//初始化一个LinkedHashMap,并且第三个参数是true,这表明这个LinkedHashMap是按照按访问顺序排序的.
    }

(3)已经满了的缓存:每次使用LruCache的时候:调用put(K
key, V value)或者调用get(K key) 时候都会调用trimToSize(int maxSize)检测一下LruCache是否已经满了.

(4)被清理掉:同上面一样,在调用trimToSize检测是否已经满来的时候,如果已经满来就会清理掉,看看源码:

    public void trimToSize(int maxSize) {
        while (true) {//一个死循环:直到当前LruCache没有满才会跳出这个循环
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {//异常错误检测:当前已经缓存的数据数量不能为负数,并且没有数据的时候其数据数量不能大于0
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {//如果已经缓存的数据数量小于最大容量(没有满),则跳出循环
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();//通过LinkedHashMap来获取最不常用的一个键值对数据
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);  //删除这个数据
                size -= <span style="color:#FF0000;">safeSizeOf</span>(key, value); //获取这个删除的键值对数据所占用的容量(有可能是1,也有可能是计算的图片占用的内存,也有可能是其他)
                evictionCount++;
            }

            <span style="color:#FF0000;">entryRemoved</span>(true, key, value, null); //回调用entryRemoved,一般用于通知客户端,这个键值对数据已经被清理出LruCache了
        }
    }	

上面的说明已经很清楚了,不过红色标识的两个方法需要下面来说说:

  • <span style="color:#FF0000;">safeSizeOf<span style="color:#000000;">来看看函数原型:</span></span>
        private int safeSizeOf(K key, V value) {
            int result = sizeOf(key, value);
            if (result < 0) {
                throw new IllegalStateException("Negative size: " + key + "=" + value);
            }
            return result;
        }

    看到了吗,其实就是调用了sizeOf这个方法.

  • entryRemoved 来看看函数原型:   protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
    是一个空的函数,其实这个方法时给子类重写的,如果你想要知道你的LruCache在悄悄删除数据的时候能够告知你的话,你就需要重写这个方法,比如:你创建一个存储bitmap的数据的LruCache,你可能想要在LruCache清理掉这个数据的时候,自己再调用这个bitmap的recycle()来是否资源的话,你就需要重写entryRemoved了.

到了这里,应该对LruCache比较了解,不过一个问题由此出来了,不知道大家有没有想到:既然LruCache会自己清理掉最不常用的键值对数据,可是最不常用也不代表就不会使用呀!如果,突然使用了已经被清理掉的键值对数据这么办!

举个例子:我的listview来显示100个菜单项,每一个菜单项来显示一个图片,每一个图片都有100k大小,我的LruCache最大容量只有1000k,这样一来当我滑动菜单的时候肯定LruCache会清除掉很多图片.那这么办呢?如果我能够知道已经清理掉的数据需要再次使用就可以了,那我可以重新创建.

为了解决这个问题,LruCache里面的方法get(K
key)会去检测当前获取的数据是否为null,如果是null,则调用方法create来创建.默认情况下create这个方法直接返回 null;所以你自己的LruCache
是需要重写这个方法的.下面来看看get(K key)方法的源码:

    public final V get(K key) {//通过key来获取存储的数据
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);//其实是通过LinkedHashMap来获取的
            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);//如果当前<font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><span style="color:#FF0000;"><span style="color:#000000;"><span style="color:#FF0000;">LruCache</span></span></span></font></font></font></font></font></font>没有这个数据,则调用create创建
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;//更新自己创建的数量
            mapValue = map.put(key, createdValue);//将新创建的数据加入到LinkedHashMap里面

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);//如果冲突来再次尝试
            } else {
                size += safeSizeOf(key, createdValue);//更新当前<font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><span style="color:#FF0000;"><span style="color:#000000;"><span style="color:#FF0000;">LruCache</span></span></span></font></font></font></font></font></font> 已经存储数据的数量
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);//调用entryRemoved通知已经清理掉了一个数据
            return mapValue;
        } else {
            trimToSize(maxSize);//检测是否已经满了
            return createdValue;
        }
    }

说道到这里,我想已经说的很清楚了.
LruCache
这个工具类其实比较简单的.除了上面我说的特点外,还有其他的一些不太常用的东西,下面来看看里面的几个源方法:

     public final V put(K key, V value) {//往<font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><span style="color:#FF0000;"><span style="color:#000000;"><span style="color:#FF0000;">LruCache</span></span></span></font></font></font></font></font></font></font></font></font></font>里面加入数据
        if (key == null || value == null) {

            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;//加入数据个数的更新
            size += safeSizeOf(key, value);//<font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><font color="#FF0000"><font color="#000000"><span style="color:#FF0000;"><span style="color:#000000;"><span style="color:#FF0000;">LruCache</span></span></span></font></font></font></font></font></font></font></font></font></font> 存储数据的数量更新
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        trimToSize(maxSize);////检测是否已经满了
        return previous;
    }
    public final void evictAll() {//清理掉所有数据
        trimToSize(-1); // -1 will evict 0-sized elements
    }
时间: 2024-10-27 06:01:52

LruCache的终极解析的相关文章

LruCache源码解析

前言 最近项目要用到Picasso,所以就看了一下Picasso里面的源码,发现里面的内存缓存主要用的LruCache这个类,就去看了一下它的相关的东西,还是挺有收获的. 正文 我一般看类源码喜欢以构造方法作为突破口,然后从它暴露出来的我们使用的最多的那些方法切入,一点一点的把它捋清除,这次基本上也是这个思路. 构造方法 /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maxi

android之LruCache源码解析

移动设备开发中,由于移动设备(手机等)的内存有限,所以使用有效的缓存技术是必要的.android提供来一个缓存工具类LruCache,开发中我们会经常用到,下面来他是如何实现的. 在package android.util包里面有对LruCache定义的java文件.为了能准确的理解LruCache,我们先来看看原文的说明: * A cache that holds strong references to a limited number of values. Each time * a va

C++11引用临时变量的终极解析

工作中遇到一个引用临时变量的问题,经过两天的学习,私以为:不仅弄明白了这个问题,还有些自己的独到见解. 这里使用一个简单的例子来把自己的学习过程和理解献给大家,如果有什么问题请不吝指正. *************************Code************************* class Dog { public: Dog(){} virtual ~Dog(){} }; void NonConstReference (Dog & dog ) { //tell the dog

Android源代码解析之(十三)--&amp;gt;apk安装流程

转载请标明出处:一片枫叶的专栏 上一篇文章中给大家分析了一下android系统启动之后调用PackageManagerService服务并解析系统特定文件夹.解析apk文件并安装的过程,这个安装过程实际上是没有图形界面的,底层调用的是我们平时比較熟悉的adb命令,那么我们平时安装apk文件的时候大部分是都过图形界面安装的,那么这样的方式安装apk详细的流程是如何的呢? 本文我们就来详细看一下apk的详细安装过程,通过本文的学习希望帮助大家大概的了解到Android系统安装Apk文件的基本流程.好

PK1325-Android从程序员到架构师之路高

随笔背景:在很多时候,很多入门不久的朋友都会问我:我是从其他语言转到程序开发的,有没有一些基础性的资料给我们学习学习呢,你的框架感觉一下太大了,希望有个循序渐进的教程或者视频来学习就好了.对于学习有困难不知道如何提升自己可以加扣:1225462853进行交流得到帮助,获取学习资料. 下载地址:http://pan.baidu.com/s/1jI05TPW 学前基础 1.掌握Java基本语法,了解面向对象编程特点,有基本Java语言编程经验 2.了解计算机系统.数据结构基础知识,掌握SQL基本语法

征服Spark as a Service

Spark是当今大数据领域最活跃最热门的高效的大数据通用计算平台,基于RDD,Spark成功的构建起了一体化.多元化的大数据处理体系,在“One Stack to rule them all”思想的引领下,Spark成功的使用Spark SQL.Spark Streaming.MLLib.GraphX近乎完美的解决了大数据中Batch Processing.Streaming Processing.Ad-hoc Query等三大核心问题,更为美妙的是在Spark中Spark SQL.Spark

Android缓存机制&amp;一个缓存框架推荐

1.先推荐一个轻量级缓存框架--ACache(ASimpleCache) ACache介绍: ACache类似于SharedPreferences,但是比SharedPreferences功能更加强大,SharedPreferences只能保存一些基本数据类型.Serializable.Bundle等数据, 而Acache可以缓存如下数据: 普通的字符串.JsonObject.JsonArray.Bitmap.Drawable.序列化的java对象,和 byte数据. 主要特色: 1:轻,轻到只

SonarQube分析报告无法上传的问题

-- 终极解析办法 INSERT INTO `sonarqube`.`ce_queue` (`uuid`, `task_type`, `component_uuid`, `status`, `submitter_login`, `started_at`, `created_at`, `updated_at`) VALUES ('AVYIhJ4YMA7l-90atYoi', 'REPORT', 'AVYIhJ4YMA7l-90atYoi', 'PENDING', 'Administrator',

Android Json数据的解析+ListView图文混排+缓存算法Lrucache 仿知乎

前几天心血来潮,打算根据看知乎的API自己做一个小知乎,定制的过程遇到ListView的优化问题及图片未缓存重加载等等许多问题,解决了以后打算和博友分享一下. 接口数据:http://api.kanzhihu.com/getpostanswers/20150925/archive 首先,Json数据太常用,相信每一位开发者Json的解析都是必备的.我们要准备以下知识: JavaBean,枚举你需要的元素,用来存储数据. 异步加载网络内容的必备途径,多线程加载+AsyncTask两种方式. Jso