简析Android中LruCache缓存类

/***************************************************

 * TODO: description .

 * @author: gao_chun

 * @since:  2015-4-7

 * @version: 1.0.0

 * @remark: 转载请注明出处

 **************************************************/

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在
LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

  在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

  为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

  你的设备可以为每个应用程序分配多大的内存?

  设备屏幕上一次最多能显示多少张图片?

有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?

  你的设备的屏幕大小和分辨率分别是多少?

一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。

  图片的尺寸和大小,还有每张图片会占据多少内存空间。

  图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?

如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。

  你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

  并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。

搜索并整理了一些资料:

  1. package android.util;
  2. import java.util.LinkedHashMap;
  3. import java.util.Map;
  4. /**
  5. * A cache that holds strong references to a limited number of values. Each time
  6. * a value is accessed, it is moved to the head of a queue. When a value is
  7. * added to a full cache, the value at the end of that queue is evicted and may
  8. * become eligible for garbage collection.
  9. * Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。
  10. * 当cache已满的时候加入新的item时,在队列尾部的item会被回收。
  11. * <p>If your cached values hold resources that need to be explicitly released,
  12. * override {@link #entryRemoved}.
  13. * 如果你cache的某个值需要明确释放,重写entryRemoved()
  14. * <p>If a cache miss should be computed on demand for the corresponding keys,
  15. * override {@link #create}. This simplifies the calling code, allowing it to
  16. * assume a value will always be returned, even when there‘s a cache miss.
  17. * 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。
  18. * <p>By default, the cache size is measured in the number of entries. Override
  19. * {@link #sizeOf} to size the cache in different units. For example, this cache
  20. * is limited to 4MiB of bitmaps: 默认cache大小是测量的item的数量,重写sizeof计算不同item的大小。
  21. * <pre>   {@code
  22. *   int cacheSize = 4 * 1024 * 1024; // 4MiB
  23. *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
  24. *       protected int sizeOf(String key, Bitmap value) {
  25. *           return value.getByteCount();
  26. *       }
  27. *   }}</pre>
  28. *
  29. * <p>This class is thread-safe. Perform multiple cache operations atomically by
  30. * synchronizing on the cache: <pre>   {@code
  31. *   synchronized (cache) {
  32. *     if (cache.get(key) == null) {
  33. *         cache.put(key, value);
  34. *     }
  35. *   }}</pre>
  36. *
  37. * <p>This class does not allow null to be used as a key or value. A return
  38. * value of null from {@link #get}, {@link #put} or {@link #remove} is
  39. * unambiguous: the key was not in the cache.
  40. * 不允许key或者value为null  当get(),put(),remove()返回值为null时,key相应的项不在cache中
  41. */
  42. public class LruCache<K, V> {
  43. private final LinkedHashMap<K, V> map;
  44. /** Size of this cache in units. Not necessarily the number of elements. */
  45. private int size;
    //已经存储的大小
  46. private int maxSize;
    //规定的最大存储空间
  47. private int putCount;
     //put的次数
  48. private int createCount;
     //create的次数
  49. private int evictionCount;
     //回收的次数
  50. private int hitCount;
     //命中的次数
  51. private int missCount;
     //丢失的次数
  52. /**
  53. * @param maxSize for caches that do not override {@link #sizeOf}, this is
  54. *     the maximum number of entries in the cache. For all other caches,
  55. *     this is the maximum sum of the sizes of the entries in this cache.
  56. */
  57. public LruCache(int maxSize) {
  58. if (maxSize <= 0) {
  59. throw new IllegalArgumentException("maxSize <= 0");
  60. }
  61. this.maxSize = maxSize;
  62. this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
  63. }
  64. /**
  65. * Returns the value for {@code key} if it exists in the cache or can be
  66. * created by {@code #create}. If a value was returned, it is moved to the
  67. * head of the queue. This returns null if a value is not cached and cannot
  68. * be created. 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
  69. * 如果item的value没有被cache或者不能被创建,则返回null。
  70. */
  71. public final V get(K key) {
  72. if (key == null) {
  73. throw new NullPointerException("key == null");
  74. }
  75. V mapValue;
  76. synchronized (this) {
  77. mapValue = map.get(key);
  78. if (mapValue != null) {
  79. hitCount++;  //命中
  80. return mapValue;
  81. }
  82. missCount++;  //丢失
  83. }
  84. /*
  85. * Attempt to create a value. This may take a long time, and the map
  86. * may be different when create() returns. If a conflicting value was
  87. * added to the map while create() was working, we leave that value in
  88. * the map and release the created value.
  89. * 如果丢失了就试图创建一个item
  90. */
  91. V createdValue = create(key);
  92. if (createdValue == null) {
  93. return null;
  94. }
  95. synchronized (this) {
  96. createCount++;//创建++
  97. mapValue = map.put(key, createdValue);
  98. if (mapValue != null) {
  99. // There was a conflict so undo that last put
  100. //如果前面存在oldValue,那么撤销put()
  101. map.put(key, mapValue);
  102. } else {
  103. size += safeSizeOf(key, createdValue);
  104. }
  105. }
  106. if (mapValue != null) {
  107. entryRemoved(false, key, createdValue, mapValue);
  108. return mapValue;
  109. } else {
  110. trimToSize(maxSize);
  111. return createdValue;
  112. }
  113. }
  114. /**
  115. * Caches {@code value} for {@code key}. The value is moved to the head of
  116. * the queue.
  117. *
  118. * @return the previous value mapped by {@code key}.
  119. */
  120. public final V put(K key, V value) {
  121. if (key == null || value == null) {
  122. throw new NullPointerException("key == null || value == null");
  123. }
  124. V previous;
  125. synchronized (this) {
  126. putCount++;
  127. size += safeSizeOf(key, value);
  128. previous = map.put(key, value);
  129. if (previous != null) {
     //返回的先前的value值
  130. size -= safeSizeOf(key, previous);
  131. }
  132. }
  133. if (previous != null) {
  134. entryRemoved(false, key, previous, value);
  135. }
  136. trimToSize(maxSize);
  137. return previous;
  138. }
  139. /**
  140. * @param maxSize the maximum size of the cache before returning. May be -1
  141. *     to evict even 0-sized elements.
  142. *  清空cache空间
  143. */
  144. private void trimToSize(int maxSize) {
  145. while (true) {
  146. K key;
  147. V value;
  148. synchronized (this) {
  149. if (size < 0 || (map.isEmpty() && size != 0)) {
  150. throw new IllegalStateException(getClass().getName()
  151. + ".sizeOf() is reporting inconsistent results!");
  152. }
  153. if (size <= maxSize) {
  154. break;
  155. }
  156. Map.Entry<K, V> toEvict = map.eldest();
  157. if (toEvict == null) {
  158. break;
  159. }
  160. key = toEvict.getKey();
  161. value = toEvict.getValue();
  162. map.remove(key);
  163. size -= safeSizeOf(key, value);
  164. evictionCount++;
  165. }
  166. entryRemoved(true, key, value, null);
  167. }
  168. }
  169. /**
  170. * Removes the entry for {@code key} if it exists.
  171. * 删除key相应的cache项,返回相应的value
  172. * @return the previous value mapped by {@code key}.
  173. */
  174. public final V remove(K key) {
  175. if (key == null) {
  176. throw new NullPointerException("key == null");
  177. }
  178. V previous;
  179. synchronized (this) {
  180. previous = map.remove(key);
  181. if (previous != null) {
  182. size -= safeSizeOf(key, previous);
  183. }
  184. }
  185. if (previous != null) {
  186. entryRemoved(false, key, previous, null);
  187. }
  188. return previous;
  189. }
  190. /**
  191. * Called for entries that have been evicted or removed. This method is
  192. * invoked when a value is evicted to make space, removed by a call to
  193. * {@link #remove}, or replaced by a call to {@link #put}. The default
  194. * implementation does nothing.
  195. * 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用,
  196. * 或者替换item值时put调用,默认实现什么都没做。
  197. * <p>The method is called without synchronization: other threads may
  198. * access the cache while this method is executing.
  199. *
  200. * @param evicted true if the entry is being removed to make space, false
  201. *     if the removal was caused by a {@link #put} or {@link #remove}.
  202. * true---为释放空间被删除;false---put或remove导致
  203. * @param newValue the new value for {@code key}, if it exists. If non-null,
  204. *     this removal was caused by a {@link #put}. Otherwise it was caused by
  205. *     an eviction or a {@link #remove}.
  206. */
  207. protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
  208. /**
  209. * Called after a cache miss to compute a value for the corresponding key.
  210. * Returns the computed value or null if no value can be computed. The
  211. * default implementation returns null.
  212. * 当某Item丢失时会调用到,返回计算的相应的value或者null
  213. * <p>The method is called without synchronization: other threads may
  214. * access the cache while this method is executing.
  215. *
  216. * <p>If a value for {@code key} exists in the cache when this method
  217. * returns, the created value will be released with {@link #entryRemoved}
  218. * and discarded. This can occur when multiple threads request the same key
  219. * at the same time (causing multiple values to be created), or when one
  220. * thread calls {@link #put} while another is creating a value for the same
  221. * key.
  222. */
  223. protected V create(K key) {
  224. return null;
  225. }
  226. private int safeSizeOf(K key, V value) {
  227. int result = sizeOf(key, value);
  228. if (result < 0) {
  229. throw new IllegalStateException("Negative size: " + key + "=" + value);
  230. }
  231. return result;
  232. }
  233. /**
  234. * Returns the size of the entry for {@code key} and {@code value} in
  235. * user-defined units.  The default implementation returns 1 so that size
  236. * is the number of entries and max size is the maximum number of entries.
  237. * 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
  238. * <p>An entry‘s size must not change while it is in the cache.
  239. */
  240. protected int sizeOf(K key, V value) {
  241. return 1;
  242. }
  243. /**
  244. * Clear the cache, calling {@link #entryRemoved} on each removed entry.
  245. * 清空cacke
  246. */
  247. public final void evictAll() {
  248. trimToSize(-1); // -1 will evict 0-sized elements
  249. }
  250. /**
  251. * For caches that do not override {@link #sizeOf}, this returns the number
  252. * of entries in the cache. For all other caches, this returns the sum of
  253. * the sizes of the entries in this cache.
  254. */
  255. public synchronized final int size() {
  256. return size;
  257. }
  258. /**
  259. * For caches that do not override {@link #sizeOf}, this returns the maximum
  260. * number of entries in the cache. For all other caches, this returns the
  261. * maximum sum of the sizes of the entries in this cache.
  262. */
  263. public synchronized final int maxSize() {
  264. return maxSize;
  265. }
  266. /**
  267. * Returns the number of times {@link #get} returned a value that was
  268. * already present in the cache.
  269. */
  270. public synchronized final int hitCount() {
  271. return hitCount;
  272. }
  273. /**
  274. * Returns the number of times {@link #get} returned null or required a new
  275. * value to be created.
  276. */
  277. public synchronized final int missCount() {
  278. return missCount;
  279. }
  280. /**
  281. * Returns the number of times {@link #create(Object)} returned a value.
  282. */
  283. public synchronized final int createCount() {
  284. return createCount;
  285. }
  286. /**
  287. * Returns the number of times {@link #put} was called.
  288. */
  289. public synchronized final int putCount() {
  290. return putCount;
  291. }
  292. /**
  293. * Returns the number of values that have been evicted.
  294. * 返回被回收的数量
  295. */
  296. public synchronized final int evictionCount() {
  297. return evictionCount;
  298. }
  299. /**
  300. * Returns a copy of the current contents of the cache, ordered from least
  301. * recently accessed to most recently accessed. 返回当前cache的副本,从最近最少访问到最多访问
  302. */
  303. public synchronized final Map<K, V> snapshot() {
  304. return new LinkedHashMap<K, V>(map);
  305. }
  306. @Override public synchronized final String toString() {
  307. int accesses = hitCount + missCount;
  308. int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
  309. return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
  310. maxSize, hitCount, missCount, hitPercent);
  311. }
  312. }
时间: 2024-07-28 17:36:16

简析Android中LruCache缓存类的相关文章

android 使用LruCache缓存网络图片

加载图片,图片如果达到一定的上限,如果没有一种合理的机制对图片进行释放必然会引起程序的崩溃. 为了避免这种情况,我们可以使用Android中LruCache来缓存下载的图片,防止程序出现OOM. 打开activity_main.xml作为程序的主布局,加入如下代码: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.an

Android中的缓存处理

一.缓存介绍 (一).Android中缓存的必要性: 1.没有缓存的弊端: 流量开销:对于客户端--服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量. 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕. 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存 2.缓存带来的好处: 1. 服务器的压力大大减小: 2. 客户端的响应速度大大变快(用户体验好): 3. 客户端的数据加载出错情况大大较少,大大提高了应有的稳定性(用户体验好): 4.

使用Android新式LruCache缓存图片,基于线程池异步加载图片

import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import a

Android中的Matrix类介绍

Matrix顾名思义就是大学的线性代数中的矩阵,关于矩阵的基本知识和加减乘除运算这里不再赘述. Android中的Matrix类是一个比较简单的3x3的3阶矩阵,结构如下: float matrix = {MSCALE_X, MSKEW_X, MTRANS_X, MSKEW_Y, MSCALE_Y, MTRANS_Y, MPERSP_0, MPERSP_1, MPERSP_2 }; 结构如上:其中 MSCALE_X和MSCALE_Y分别是控制X轴和Y轴方向的缩放,MSKEW_X和MSKEW_Y是

(转)Android中的基类—抽取出来公共的方法

在Android中,一般来说一个应用会存在几十个页面,并且一个应用一般也会使用一个特定的主题,其中的页面的风格也是一致的,并且页面中的动画效果.页面的切换效果等也应该保持同样的风格,那么就需要一个基类,来完成页面的基础设置,这就是所谓的基类. 界面统一管理原因 当应用中的界面达到一定数量级时,如几十个界面,便于项目管理 提高界面的处理速度 避免由于多Activity导致的问题 界面统一管理的基础 当应用中的界面达到一定数量级时,为了能够让用户更容易操作上手,产品设计时会将界面显示风格进行统一.

(转)谈谈Android中的Rect类——奇葩的思维

最近在工作中遇到了一些问题,总结下来就是Android中Rect这个类造成的.不得不说,不知道Android SDK的开发人员是怎么想的, 这个类设计的太奇葩了.首先介绍一下Rect类:Rect类主要用于表示坐标系中的一块矩形区域,并可以对其做一些简单操作.这块矩形区域,需要用左上右下两个坐标点表示(left,top,right,bottom),你也可以获取一个Rect实例的Width和Height.就在这里,奇葩的事情来了,作为一个有一点经验的做图像或者矩阵运算或者编程的程序员来说,大家的共识

Android中的基类—抽取出来公共的方法

在Android中,一般来说一个应用会存在几十个页面,并且一个应用一般也会使用一个特定的主题,其中的页面的风格也是一致的,并且页面中的动画效果.页面的切换效果等也应该保持同样的风格,那么就需要一个基类,来完成页面的基础设置,这就是所谓的基类. 界面统一管理原因 当应用中的界面达到一定数量级时,如几十个界面,便于项目管理 提高界面的处理速度 避免由于多Activity导致的问题 界面统一管理的基础 当应用中的界面达到一定数量级时,为了能够让用户更容易操作上手,产品设计时会将界面显示风格进行统一.

Android 中LruCache 原理与编程

Android用LruCache来取代原来强引用和软引用实现内存缓存,因为据说自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放. LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用.根据LinkedHashMap的结构原理,最新的应该在尾端,旧的应该在头部.如果添加的数据大于设置的最大值,就删除最先缓存(头部)的数据来调整内存.他的主要原理在trimToSize方法中.需要了解两个主要的变量size和maxSize maxS

Android中的缓存机制与实现

分步阅读 Android开发本质上就是手机和互联网中的web服务器之间进行通信,就必然需要从服务端获取数据,而反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android中可通过二级缓存来减少频繁的网络操作,减少流量.提升性能. 方法/步骤 二级缓存工作机制 所谓二级缓存实际上并不复杂,当Android端需要获得数据时比如获取网络中的图片,我们首先从内存中查找(按键查找),内存中没有的再从磁盘文件或sqlite中去查找,若磁盘中也没有才通过网络获取:当获得来自网络的