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);
 }
相关文章
|
16天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
1月前
|
C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
1月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
1月前
|
存储 C语言 C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(一)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
3月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
74 0
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
189 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
1月前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
55 3
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
40 1
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制