Android源代码解析之(七)-->LruCache缓存类

转载请标明出处:一片枫叶的专栏

android开发过程中常常会用到缓存。如今主流的app中图片等资源的缓存策略通常是分两级。一个是内存级别的缓存,一个是磁盘级别的缓存。

作为android系统的维护者google也开源了其缓存方案,LruCache和DiskLruCache。从android3.1開始LruCache已经作为android源代码的一部分维护在android系统中。为了兼容曾经的版本号android的support-v4包也提供了LruCache的维护,假设App须要兼容到android3.1之前的版本号就须要使用support-v4包中的LruCache,假设不须要兼容到android3.1则直接使用android源代码中的LruCache就可以,这里须要注意的是DiskLruCache并非android源代码的一部分。

在LruCache的源代码中。关于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.

cache对象通过一个强引用来訪问内容。每次当一个item被訪问到的时候,这个item就会被移动到一个队列的队首。当一个item被加入到已经满了的队列时,这个队列的队尾的item就会被移除。

事实上这个实现的过程就是LruCache的缓存策略。即Lru–>(Least recent used)最少近期使用算法。

以下我们详细看一下LruCache的实现:

public class LruCache<K, V> {
    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;

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    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);
    }

    /**
     * Sets the size of the cache.
     *
     * @param maxSize The new maximum size.
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

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

    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     */
    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;
        }
    }

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     */
    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;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1
     *            to evict even 0-sized elements.
     */
    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);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     */
    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;
    }

    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * <p>If a value for {@code key} exists in the cache when this method
     * returns, the created value will be released with {@link #entryRemoved}
     * and discarded. This can occur when multiple threads request the same key
     * at the same time (causing multiple values to be created), or when one
     * thread calls {@link #put} while another is creating a value for the same
     * key.
     */
    protected V create(K key) {
        return null;
    }

    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry‘s size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /**
     * Returns the number of times {@link #get} returned a value that was
     * already present in the cache.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * Returns the number of times {@link #put} was called.
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * Returns the number of values that have been evicted.
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

能够看到LruCache初始化的时候须要使用泛型,一般的我们这样初始化LruCache对象:

// 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };  

这里我们假设通过String作为key保存bitmap对象,同一时候须要传递一个int型的maxSize数值。主要用于设置LruCache链表的最大值。

查看其构造方法:

// 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };  

能够看到其基本的是初始化了maxSize和map链表对象。

然后查看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;
    }

须要传递两个參数:K和V,首先做了一下參数的推断,然后定义一个保存前一个Value值得暂时变量。让putCount(put运行的次数)自增,让map的size大小自增。

须要注意的是这里的

previous = map.put(key, value);

我们看一下这里的map.put()的详细实现:

@Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }

将Key与Value的值压入Map中,这里推断了一下假设map中已经存在该key,value键值对,则不再压入map,并将Value值返回,否则将该键值对压入Map中。并返回null;

返回继续put方法:

previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }

能够看到这里我们推断map.put方法的返回值是否为空。假设不为空的话,则说明我们刚刚并没有将我么你的键值对压入Map中,所以这里的size须要自减;

然后以下:

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

这里推断previous是否为空,假设不为空的话,调用了一个空的实现方法entryRemoved(),也就是说我们能够实现自己的LruCache并在加入缓存的时候若存在该缓存能够重写这种方法;

以下调用了trimToSize(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);
        }
    }

该方法主要是推断该Map的大小是否已经达到阙值,若达到,则将Map队尾的元素(最不常使用的元素)remove掉。

总结:

LruCache put方法,将键值对压入Map数据结构中。若这是Map的大小已经大于LruCache中定义的最大值,则将Map中最早压入的元素remove掉;

查看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;
        }
    }

能够看到參数值为Key。简单的理解就是通过key值从map中取出Value值。

详细来说,推断map中是否含有key值value值。若存在。则hitCount(击中元素数量)自增,并返回Value值。若没有击中,则运行create(key)方法,这里看到create方法是一个空的实现方法,返回值为null。所以我们能够重写该方法,在调用get(key)的时候若没有找到value值,则自己主动创建一个value值并压入map中。

总结:

  • LruCache,内部使用Map保存内存级别的缓存
  • LruCache使用泛型能够设配各种类型
  • LruCache使用了Lru算法保存数据(最短最少使用least recent use)
  • LruCache仅仅用使用put和get方法压入数据和取出数据

另外对android源代码解析方法感兴趣的可參考我的:

android源代码解析之(一)–>android项目构建过程

android源代码解析之(二)–>异步消息机制

android源代码解析之(三)–>异步任务AsyncTask

android源代码解析之(四)–>HandlerThread

android源代码解析之(五)–>IntentService

android源代码解析之(六)–>Log



本文以同步至github中:https://github.com/yipianfengye/androidSource。欢迎star和follow


原文地址:https://www.cnblogs.com/zhchoutai/p/8454198.html

时间: 2024-12-17 02:20:41

Android源代码解析之(七)--&gt;LruCache缓存类的相关文章

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

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

Android源代码解析之(三)--&amp;gt;异步任务AsyncTask

转载请标明出处:一片枫叶的专栏 上一篇文章中我们解说了android中的异步消息机制. 主要解说了Handler对象的使用方式.消息的发送流程等.android的异步消息机制是android中多任务处理的基础,Handler是整个android应用层体系异步消息传递的基础组件,通过对Handler源代码的解析的解析相信大家对android中的异步消息机制有了一个大概的了解.很多其它关于android中的异步消息机制的知识可參考我的:android源代码解析之(二)–>异步消息机制 android

Android源代码解析之(四)--&amp;gt;HandlerThread

转载请标明出处:一片枫叶的专栏 上一篇文章中我们解说了AsyncTast的基本使用以及实现原理,我们知道AsyncTask内部是通过线程池和Handler实现的.通过对线程池和handler的封装实现了对异步任务操作.很多其它关于AsyncTask相关的内容,可參考我的android源代码解析之(三)–>异步任务AsyncTask 本文我们将解说HandlerThread相关的概念. HandlerThread是什么东西呢?了解一个类最好的方法就是查看类的定义,所以我们就看一下HandlerTh

Android源代码解析之(六)--&amp;gt;Log日志

转载请标明出处:一片枫叶的专栏 首先说点题外话,对于想学android framework源代码的同学,事实上能够在github中fork一份,详细地址:platform_frameworks_base 这里面基本都是android framework层的源代码了.并且近期发现了一个比較不错的github插件:OctoTree,它 是一个浏览器插件,它能够让你在Github 看代码时,左边栏会出现一个树状结构.就像我们在IDE 一样.当我们看一个项目的结构,或者想看详细的某个文件,这样就会非常方

Android volley 解析(四)之缓存篇

这是 volley 的第四篇 blog 了,写完这篇,volley 的大部分用法也都算写了一遍,所以暂时不会写 volley 的文章了,如果想看我前面写的文章,可以点这里 Android volley 解析(三)之文件上传篇 为什么要用缓存 我们知道,当客户端在请求网络数据的时候,是需要消耗流量的,特别是对于移动端用户来说,对于流量的控制要求很高.所以在做网络请求的时候,如果对数据更新要求不是特别高,往往都会用到缓存机制,一方面能减少对服务端的请求,控制流量:另一方面,当客户端在没有网络的情况下

Android 源代码解析 之 setContentView

大家在平时的开发中.对于setContentView肯定不陌生,那么对其内部的实现会不会比較好奇呢~~~有幸最终能看到一些PhoneWindow神马的源代码,今天就带大家来跑一回源代码~~ 1.Activity  setContentView 首先不用说,进入Activity的setContentView public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initActio

简析Android中LruCache缓存类

/***************************************************  * TODO: description .  * @author: gao_chun  * @since:  2015-4-7  * @version: 1.0.0  * @remark: 转载请注明出处  **************************************************/ 内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法.其中最核心

Spring源代码解析

Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.iteye.com/topic/86594 Spring源代码解析(三):Spring JDBC:http://www.iteye.com/topic/87034 Spring源代码解析(四):Spring MVC:http://www.iteye.com/topic/87692 Spring源代码解析(五

Spring源代码解析(收藏)

Spring源代码解析(收藏)Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.iteye.com/topic/86594 Spring源代码解析(三):Spring JDBC:http://www.iteye.com/topic/87034 Spring源代码解析(四):Spring MVC:http://www.iteye.com/topic/8769