【源代码】LruCache源代码剖析

上一篇分析了LinkedHashMap源代码,这个Map集合除了拥有HashMap的大部分特性之外。还拥有链表的特点,即能够保持遍历顺序与插入顺序一致。

另外。当我们将accessOrder设置为true时。能够使遍历顺序和訪问顺序一致,其内部双向链表将会依照最近最少訪问到最近最多訪问的顺序排列Entry对象,这能够用来做缓存。

这篇文章分析的LruCache并非jdk中的类,而是来自安卓,熟悉安卓内存缓存的必定对这个类不陌生。

LruCache内部维护的就是一个LinkedHashMap。

以下開始分析LruCache。

注:以下LruCache源代码来自support.v4包。

首先是这个类的成员变量:

 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;//put次数
    private int createCount;//create次数
    private int evictionCount;//回收次数
    private int hitCount;//命中次数
    private int missCount;//丢失次数

LinkedHashMap的初始化放在构造器中:

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);
    }

这里将LinkedHashMap的accessOrder设置为true。

接下来看两个最重要的方法,put和get。

首先是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) {//之前已经插入过同样的key
                size -= safeSizeOf(key, previous);//那么减去该entry的容量,由于发生覆盖
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);//这种方法默认空实现
        }
        trimToSize(maxSize);//若容量超过maxsize,将会删除近期非常少訪问的entry
        return previous;
    }

put方法无非就是调用LinkedHashMap的put方法。可是这里在调用LinkedHashMap的put方法之前,推断了key和value是否为空,也就是说LruCache不同意空键值

除此之外,put操作被加锁了,所以是线程安全的

既然是缓存,那么必定可以动态删除一些不经常使用的键值对,这个工作是由trimToSize方法完毕的:

 public void trimToSize(int maxSize) {
        while (true) {//不断删除linkedHashMap头部entry,也就是近期最少訪问的条目。直到size小于最大容量
            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);//删除最少訪问的entry
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }

这种方法不断循环删除链表首部元素。也就是近期最少訪问的元素,直到容量不超过预先定义的最大值为止。

注:LruCache在android.util包中也有一个LruCache类,可是我发现这个类的trimToSize方法是错误的:

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;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }

                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }

这里的代码将会循环删除链表尾部,也就是近期訪问最多的元素,这是不对的!所以大家在做内存缓存的时候一定要注意,看trimToSize方法是否有问题。

接下来是get方法:

public final V get(K key) {
        if (key == null) {//不同意空键
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {//线程安全
            mapValue = map.get(key);//调用LinkedHashMap的get方法
            if (mapValue != null) {
                hitCount++;//命中次数加1
                return mapValue;//返回value
            }
            missCount++;//未命中
        }

        V createdValue = create(key);//默认返回为false
        if (createdValue == null) {
            return null;
        }
        synchronized (this) {
            createCount++;//假设创建成功,那么create次数加1
            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;
        }
    }

get方法即依据key在LinkedHashMap中寻找相应的value,此方法也是线程安全的。

以上就是LruCache最重要的部分,以下再看下其它方法:

remove:

  public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous;
        synchronized (this) {
            previous = map.remove(key);//调用LinkedHashMap的remove方法
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;//返回value
    }

sizeof:这种方法用于计算每一个条目的大小,子类必须得复写这个类。

 protected int sizeOf(K key, V value) {//用于计算每一个条目的大小
        return 1;
    }

snapshot方法,返回当前缓存中全部的条目集合

 public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

总结:

1.LruCache封装了LinkedHashMap。提供了LRU缓存的功能;

2.LruCache通过trimToSize方法自己主动删除近期最少訪问的键值对。

3.LruCache不同意空键值;

4.LruCache线程安全。

5.继承LruCache时,必需要复写sizeof方法。用于计算每一个条目的大小。

时间: 2024-08-05 12:23:26

【源代码】LruCache源代码剖析的相关文章

【转】如何看懂源代码--(分析源代码方法)

如何看懂源代码--(分析源代码方法) --转至 https://blog.csdn.net/luka_ye/article/details/79565902[PS:不知道原作者是哪位大牛,受益了] 我们在写程序时,有不少时间都是在看别人的代码. 例如看小组的代码,看小组整合的守则,若一开始没规划怎么看, 就会"噜看噜苦(台语) " 不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力. 网路上有一篇关于分析看代码的方法,做为程序设计师

[转载]如何看懂源代码--(分析源代码方法)

近来想着看下hbase源码,却不知从何下手,特找来下文一读.收获颇多,且记. 原文链接:http://blog.csdn.net/challenge_c_plusplus/article/details/6680220 不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力. 网路上有一篇关于分析看代码的方法,做为程序设计师的您,不妨参考看看, 换个角度来分析. 也能更有效率的解读你想要的程序码片段. 六个章节: ( 1 )读懂程序码,使心法皆为

Android源代码 之 源代码版本名称、版本号、API级别对照表

Android源代码 之 源代码分支、版本号、支持设备列表

【Java集合源代码剖析】TreeMap源代码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085 前言 本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透TreeMap的全部源代码.对博主来说.确实须要耗费大量的时间和经历.眼下看来不大可能有这么多时间的投入.故这里意在通过于阅读源代码对TreeMap有个宏观上的把握,并就当中一些方法的实现做比較深入的分析. 红黑树简单介绍 TreeMap是基于红黑树实现的,这里仅仅对红黑树做个简单的介绍,红黑树是一种特

源代码的下载和编译

4.1下载编译和测试源代码 Android源代码由很多东西组成,一种是Android系统应用程序的代码,android SDK带的各种工具,android NDK的源代码,HAL源代码. 1.配置Android源代码下载环境 在下载Android源代码之前必须要在Linux终端执行一系列命令来配置下载环境. 第一步:创建一个用于存放下载脚本文件(repo)的目录 #  mkdir  ~/bin #  PATH=~/bin:$PATH 第二步:下载repo脚本文件 # curl https://d

3. Gradle源代码编译以及源代码分析

一. Gradle简介 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具.它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置.---来源于百度 对于自己而言,作为一个Android程序员,我自己使用就是用它来编译android程序,以前用ant,现在用gradle. 但是个人觉得在android studio gradle属性又多又乱,不知道总共有哪些属性:同时对于框架,自己也不喜欢黑盒使用它,总觉得心里没

在Android中调用C#写的WebService(附源代码)

由于项目中要使用Android调用C#写的WebService,于是便有了这篇文章.在学习的过程中,发现在C#中直接调用WebService方便得多,直接添加一个引用,便可以直接使用将WebService当做一个对象使用,利用Vs2010中的代码提示功能就能爽歪歪地把想要的东西全部点出来.在Android调用,麻烦了一点,但是也还好.主要是我们需要自己在代码中确定要调用WebService的方法名是什么,要传给WebService什么参数以及对应的参数名,另外,一些额外的信息比如soap的版本号

Android 4.4 全套源代码及子模块源代码的下载方法

博文<Android源代码下载--用git clone实现单个文件夹下载>介绍了採用git clone方法下载Android单个文件夹源代码的方法,这篇文章已经有四年的历史,这期间Google对源代码的管理站点已经进行了更改,直接採用原来的方法下载源代码已经失效. 本文介绍了在ubuntu下(在Windows下安装Cygwin,通过Cygwin也可在Windows里通过本文的下载步骤下载Android源代码)获取眼下最新的Android 4.4 全套源代码以及单个自模块源代码的下载方法.可依据