Android内存缓存LruCache源码解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,当一个新数据要添加到LruCache而此时缓存大小要满时,队尾的数据就有可能会被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:<strong>把最近最少使用的数据从队列中移除,把内存分配给最新进入的数据。</strong>

LruCache 作为内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,当一个新数据要添加到LruCache而此时缓存大小要满时,队尾的数据就有可能会被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:把最近最少使用的数据从队列中移除,把内存分配给最新进入的数据。

  • 如果LruCache缓存的某条数据明确地需要被释放,可以覆写entryRemoved(boolean evicted, K key, V oldValue, V newValue)
  • 如果LruCache缓存的某条数据通过key没有找到,可以覆写 create(K key),这简化了调用代码,即使错过一个缓存数据,也不会返回null,而会返回通过create(K key)创造的数据。
  • 如果想限制每条数据的缓存大小,可以覆写sizeOf(K key, V value),如下面设置bitmap最大缓存大小是4MB:
 int cacheSize = 4 * 1024 * 1024; // 4MiB
 LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
     protected int sizeOf(String key, Bitmap value) {
         return value.getByteCount();
     }
 }

LruCache这个类是线程安全的。自动地执行多个缓存操作通过synchronized 同步缓存:

 synchronized (cache) {
   if (cache.get(key) == null) {
       cache.put(key, value);
   }
 }

LruCache不允许null作为一个key或value,get(K)、put(K,V),remove(K)中如果key或value为null,都会抛出异常:throw new NullPointerException("key == null || value == null"),LruCache是在Android 3.1 (Honeycomb MR1)有的,如果想在3.1之前使用,可以使用support-v4兼容库。

LruCache常用方法

方法 备注
void resize(int maxSize) 更新存储大小
V put(K key, V value) 存数据,返回之前key对应的value,如果没有,返回null
V get(K key) 取出key对应的缓存数据
V remove(K key) 移除key对应的value
void evictAll() 清空缓存数据
Map<K, V> snapshot() 复制一份缓存并返回,顺序从最近最少访问到最多访问排序

LruCache的使用

  • 初始化:
 private Bitmap bitmap;
 private String STRING_KEY = "data_string";
 private LruCache<String, Bitmap> lruCache;
 private static final int CACHE_SIZE = 10 * 1024 * 1024;//10M

 lruCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
       super.entryRemoved(evicted, key, oldValue, newValue);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        //这里返回的大小用单位kb来表示的
        return value.getByteCount() / 1024;
    }
};

最大缓存设置为10M,key是String类型,value设置的是Bitmap

  • 存数据:
  if(lruCache!=null){
     lruCache.put(STRING_KEY, bitmap);
   }
  • 取数据:
 if (lruCache != null) {
     bitmap = lruCache.get(STRING_KEY);
  }
  • 清除缓存:
 if (lruCache != null) {
     lruCache.evictAll();
   }

LruCache源码分析

  • 定义变量、构造器初始化:
  private final LinkedHashMap<K, V> map;//使用LinkedHashMap来存储操作数据
    /** 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);
    }

构造函数中初始化了LinkedHashMap,参数maxSize指定了缓存的最大大小。

  • 修改缓存大小:
/**
 * 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);
}

resize(int maxSize)用来更新大小,先是加同步锁更新maxSize的值,接着调用了trimToSize()方法:

/**
 * 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();
            //从LinkedHashMap移除这条最少使用的数据
            map.remove(key);
            //缓存大小size减去移除数据的大小,如果没有覆写sizeOf,则减去的值是1
            size -= safeSizeOf(key, value);
            evictionCount++;
        }

        entryRemoved(true, key, value, 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;
 }

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

trimToSize() 循环移除最近最少使用的数据直到剩余缓存数据的大小等于小于最大缓存大小。
注:我们看到sizeOf()和entryRemoved()都是protected来修饰的,即可以被覆写,如果sizeOf()没有被覆写,那么变量size 代表的是缓存数据的数量,maxSize代表的是最大数量,如果覆写sizeOf(),如:

 @Override
  protected int sizeOf(String key, BitmapDrawable value) {
      return value.getBitmap().getByteCount() / 1024;
  }

此时size 代表的是缓存数据的大小,maxSize代表的是最大缓存大小。

  • 存数据:
/**
 * 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) {
    //key或value为空直接抛异常
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }

    V previous;
    synchronized (this) {
        //添加数据次数+1
        putCount++;
         //缓存数据的大小增加
        size += safeSizeOf(key, value);
         //添加缓存数据,添加之前如果key值对应的value不为空,则newValue会覆盖oldValue,并返回oldValue;
         //如果key值对应的value为空,则返回Null
        previous = map.put(key, value);
        if (previous != null) {
           //之前的oldValue不为空,则在缓存size中减去oldValue
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    //重新检查缓存大小
    trimToSize(maxSize);
    return previous;
}

调用LinkedHashMap的put(key, value)添加缓存数据时,在添加之前如果key值对应的value不为空,则newValue会覆盖oldValue,并返回oldValue;如果key值对应的value为空,则返回null,接着根据返回值来重新设置缓存size和最大缓存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) {
         //通过key取数据
         mapValue = map.get(key);
         if (mapValue != null) {
             //如果取到了数据,命中次数+1
             hitCount++;
             return mapValue;
         }
         //没有取到数据,未命中此时+1
         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.
      */
     //如果没有覆写create(),默认create()方法返回的null
     V createdValue = create(key);    
     if (createdValue == null) {
         return null;
     }
     //如果覆写了create(),即根据key值手动创造了value,则继续往下执行
     synchronized (this) {
          //创造数据次数+1
         createCount++;
         //尝试将数据添加到缓存中
         mapValue = map.put(key, createdValue);
         
         if (mapValue != null) {
             // There was a conflict so undo that last put
             //mapValue不为空,说明之前的key对应的是有数据的,那么就跟我们手动创建的数据冲突了,
             //所以执行撤消操作,重新把mapValue添加到缓存中,用mapValue去覆盖createdValue
             map.put(key, mapValue);
         } else {
             //如果mapValue为空,说明之前的key值对应的value确实为空,我们手动添加createdValue后,
             //需要重新计算缓存size的大小
             size += safeSizeOf(key, createdValue);
         }
     }

     if (mapValue != null) {
         entryRemoved(false, key, createdValue, mapValue);
         return mapValue;
     } else {
         trimToSize(maxSize);
         return createdValue;
     }
 }

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

取数据的流程大致是这样:
1、首先通过map.get(key)来尝试取出value,如果value存在,则直接返回value;
2、如果value不存在,则执行下面的create(key),如果create()没有被覆写,则直接返回null;
3、如果create()被覆写了,即通过key值创建了一个createdValue,那么尝试通过mapValue = map.put(key, createdValue)把createdValue添加到缓存中去,mapValue是put()方法的返回值,mapValue代表的是key之前对应的值,如果mapValue不为空,说明之前的key对应的是有数据的,那么就跟我们手动创建的数据冲突了,所以执行撤消操作,重新把mapValue添加到缓存中,用mapValue去覆盖createdValue,最后再重新计算缓存大小。

  • 移除数据:
 /**
  * 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;
 }

调用map.remove(key)通过key值删除缓存中key对应的value,然后重新计算缓存大小,并返回删除的value。

  • 其他一些方法:
  //清空缓存
 public final void evictAll() {
      trimToSize(-1); // -1 will evict 0-sized elements
  }

 public synchronized final int size() {
     return size;
 }

 public synchronized final int maxSize() {
      return maxSize;
 }

 public synchronized final int hitCount() {
     return hitCount;
 }

 public synchronized final int missCount() {
     return missCount;
 }

public synchronized final int createCount() {
     return createCount;
 }

 public synchronized final int putCount() {
     return putCount;
 }

 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);
 }
相关文章
|
13天前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术特性与用户体验的深度解析
在移动操作系统的战场上,安卓和iOS一直占据着主导地位。本文将深入探讨这两大平台的核心技术特性,以及它们如何影响用户的体验。我们将从系统架构、应用生态、安全性能和创新功能四个方面进行比较,帮助读者更好地理解这两个系统的异同。
43 3
|
5天前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
22 4
Android性能测试——发现和定位内存泄露和卡顿
|
4天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
13 0
|
17天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
69 0
|
17天前
|
开发者 算法 虚拟化
惊爆!Uno Platform 调试与性能分析终极攻略,从工具运用到代码优化,带你攻克开发难题成就完美应用
【8月更文挑战第31天】在 Uno Platform 中,调试可通过 Visual Studio 设置断点和逐步执行代码实现,同时浏览器开发者工具有助于 Web 版本调试。性能分析则利用 Visual Studio 的性能分析器检查 CPU 和内存使用情况,还可通过记录时间戳进行简单分析。优化性能涉及代码逻辑优化、资源管理和用户界面简化,综合利用平台提供的工具和技术,确保应用高效稳定运行。
30 0
|
17天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
全面解析TensorFlow Lite:从模型转换到Android应用集成,教你如何在移动设备上轻松部署轻量级机器学习模型,实现高效本地推理
【8月更文挑战第31天】本文通过技术综述介绍了如何使用TensorFlow Lite将机器学习模型部署至移动设备。从创建、训练模型开始,详细演示了模型向TensorFlow Lite格式的转换过程,并指导如何在Android应用中集成该模型以实现预测功能,突显了TensorFlow Lite在资源受限环境中的优势及灵活性。
44 0
|
19天前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
1月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
53 6
|
4天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理

热门文章

最新文章

推荐镜像

更多