Android内存缓存LruCache源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,当一个新数据要添加到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);
 }
相关文章
|
1月前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
38 6
|
1月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
1月前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
24天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
21天前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
26天前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
23天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
1月前
|
监控 Java Android开发
深入探讨Android系统的内存管理机制
本文将深入分析Android系统的内存管理机制,包括其内存分配、回收策略以及常见的内存泄漏问题。通过对这些方面的详细讨论,读者可以更好地理解Android系统如何高效地管理内存资源,从而提高应用程序的性能和稳定性。
68 16
|
1月前
|
存储 缓存 网络协议
如何防止DNS缓存中毒攻击(一)
DNS缓存中毒也称为DNS欺骗
51 10
|
1月前
|
缓存 网络协议 安全
如何防止DNS缓存中毒(Ⅱ)
服务器应该配置为尽可能少地依赖与其他DNS服务器的信任关系
46 10

推荐镜像

更多