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 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缓存数据是採用持有数据的强引用来保存 一定数量 的数据的.每次用到(获取)一个数据时,这个数据就会被移动(一个保存数据的)队列的头部,当往这个缓存里面增加一个新的数据时,假设这个缓存已经满了,就 会自己主动删除 这个缓存队列里面最后一个数据,这样一来使得这个删除的数据没有强引用而可以被gc回收.

      从上面的翻译,能够知道LruCache的工作原理.以下来一步一步说明他的详细实现:

      (1)怎样实现保存的数据是有一定顺序的,而且使用过一个存在的数据,这个数据就会被移动到数据队列的头部.这里採用的是LinkedHashMap.

          我们知道LinkedHashMap是保存一个键值对数据的,而且能够维护这些数据对应的顺序的.一般能够保证存储的数据依照存入的顺序或者使用的顺序的.以下来看看LruCache的构造方法: 

    public LruCache(int maxSize) {//指定缓存数据的数量
        if (maxSize <= 0) {//必须大于0
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//创建一个LinkedHashMap,而且依照訪问数据的顺序排序    
    }
      (2) 如上面的构造方法能够知道,在创建一个LruCache就须要指定其缓存数据的数量.这里要详解一下这个缓存数据的"数量"究竟是指什么:是指缓存数据对象的个数呢,还是缓存数据所占用的内存总量呢?

      答案是:都是. 能够是缓存数据的个数,也能够使缓存数据所占用内存总量,当然也能够是其它.究竟是什么,须要看你的LruCache怎样重写这种方法:sizeOf(K key, V value)

    protected int sizeOf(K key, V value) {//子类覆盖这种方法来计算出自己的缓存对于每个保存的数据所占用的量
        return 1;//默认返回1,这说明:默认情况下缓存的数量就是指缓存数据的总个数(每个数据都是1).
    }
     那假设我使用LruCache来保存bitmap的图片,而且希望缓存的容量是4M那这么做?

在原文的说明中,android给来这样一个实例:

 * <p>By default, the cache size is measured in the number of entries. Override
 * {@link #sizeOf} to size the cache in different units. For example, this cache
 * is limited to 4MiB of bitmaps:
 * <pre>   {@code
 *   int cacheSize = 4 * 1024 * 1024; // 4MiB
 *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {//保存bitmap的LruCache,容量是4M
 *       protected int sizeOf(String key, Bitmap value) {
 *           return value.getByteCount();//计算每个缓存的图片所占用内存大小
 *       }
 *   }}</pre>
     (3) 那么LruCache怎样,何时推断是否缓存已经满来,而且须要移除不经常使用的数据呢?

        事实上在LruCache里面有一个方法:trimToSize()就是用来检測一次当前是否已经满,假设满来就自己主动移除一个数据,一直到不满为止:

    public void trimToSize(int maxSize) {//默认情况下传入是上面说的最大容量的值 this.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);//用来通知这个数据已经被移除,假设你须要知道一个数据何时被移除你须要从写这种方法entryRemoved
        }
    }
      上面的源代码中我给出了说明,非常好理解.这里要注意的是trimToSize这种方法是public的,说明事实上我们自己能够调用这种方法的.这一点非常重要.记住他,你会用到的.

      以下的问题是:trimToSize这种方法何时调用呢?

       trimToSize这种方法在LruCache里面多个方法里面会被调用来检測是否已经满了,比方在往LruCache里面增加一个新的数据的方法put里面,还有在通过get(K key)这种方法获取一个数据的时候等,都会调用trimToSize来检測一次.下了来看看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;
    }
    
       (4) 细心的人在看了上面的源代码能够发现,原来对 LruCache的操作都加了synchronized来保证线程安全,是的,LruCache就是线程安全的,其它的方法也都使用来synchronized

      (5)事实上你应该立即有一个疑问:假设LruCache中已经删除了一个数据,但是如今又调用LruCache的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;
        }
    }
       从上面的分析能够知道,我们能够从写create方法来又一次创建已经不存在的数据.这种方法默认情况是什么也不做的,所以须要你自己做

    protected V create(K key) {
        return null;
    }

 






本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5135819.html,如需转载请自行联系原作者

相关文章
|
3月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
613 50
|
7月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
360 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
10月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
402 15
|
10月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
410 2
|
10月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1026 29
|
10月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
441 4
|
10月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。

推荐镜像

更多
  • DNS