内存缓存LruCache实现原理

  自己项目中一直都是用的开源的xUtils框架,包括 BitmapUtils、DbUtils、ViewUtils和HttpUtils四大模块,这四大模块都是项目中比较常用的。最近决定研究一下 xUtils的源码,用了这么久总得知道它的实现原理吧。我是先从先从BitmapUtils模块开始的。BitmapUtils和大多数图片加载框架一 样,都是基于内存-文件-网络三级缓存。也就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取,如果文件缓存没有取到,就从网络下载图片并 且加入内存和文件缓存。

  这篇帖子先分析内存缓存是如何实现的。好吧开始进入正题。

   BitmapUtils内存缓存的核心类LruMemoryCache,LruMemoryCache代码和v4包的LruCache一样,只是加了一 个存储超期的处理,这里分析LruCache源码。LRU即Least Recently Used,近期最少使用算法。也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。


        讲到LruCache不得不提一下LinkedHashMap,因为LruCache中Lru算法的实现就是通过LinkedHashMap来实现 的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一 种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整 Entry顺序链表的工作。LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存 中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到 的)移除。关于LinkedHashMap详解请前往http://www.cnblogs.com/children/archive/2012/10/02/2710624.html。


        下面看下LruCache的源码,我都注释的很详细了。


  1 /2   Copyright (C) 2011 The Android Open Source Project3  4   Licensed under the Apache License, Version 2.0 (the “License”);5   you may not use this file except in compliance with the License.6   You may obtain a copy of the License at7  8        http://www.apache.org/licenses/LICENSE-2.09  10   Unless required by applicable law or agreed to in writing, software11   distributed under the License is distributed on an “AS IS” BASIS,12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13   See the License for the specific language governing permissions and14   limitations under the License.15  /1617 package android.support.v4.util;1819 import java.util.LinkedHashMap;20 import java.util.Map;2122 /**23   Static library version of {@link android.util.LruCache}. Used to write apps24   that run on API levels prior to 12. When running on API level 12 or above,25   this implementation is still used; it does not try to switch to the26   framework’s implementation. See the framework SDK documentation for a class27   overview.28  /29 public class LruCache {30     private final LinkedHashMap map;3132     /** Size of this cache in units. Not necessarily the number of elements. /33     private int size;    //当前cache的大小34     private int maxSize; //cache最大大小3536     private int putCount;       //put的次数37     private int createCount;    //create的次数38     private int evictionCount;  //回收的次数39     private int hitCount;       //命中的次数40     private int missCount;      //未命中次数4142     /43       @param maxSize for caches that do not override {@link #sizeOf}, this is44           the maximum number of entries in the cache. For all other caches,45           this is the maximum sum of the sizes of the entries in this cache.46      /47     public LruCache(int maxSize) {48         if (maxSize <= 0) {49             throw new IllegalArgumentException(“maxSize <= 0”);50         }51         this.maxSize = maxSize;52         //将LinkedHashMap的accessOrder设置为true来实现LRU53         this.map = new LinkedHashMap(0, 0.75f, true);54     }5556     /57       Returns the value for {@code key} if it exists in the cache or can be58       created by {@code #create}. If a value was returned, it is moved to the59       head of the queue. This returns null if a value is not cached and cannot60       be created.61       通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,62       如果item的value没有被cache或者不能被创建,则返回null。63      /64     public final V get(K key) {65         if (key == null) {66             throw new NullPointerException(“key == null”);67         }6869         V mapValue;70         synchronized (this) {71             mapValue = map.get(key);72             if (mapValue != null) {73                 //mapValue不为空表示命中,hitCount+1并返回mapValue对象74                 hitCount++;75                 return mapValue;76             }77             missCount++;  //未命中78         }7980         /81           Attempt to create a value. This may take a long time, and the map82           may be different when create() returns. If a conflicting value was83           added to the map while create() was working, we leave that value in84           the map and release the created value.85           如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法86           如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去87           文件缓存中去取或者从网络下载,所以并不需要创建。88          /89         V createdValue = create(key);90         if (createdValue == null) {91             return null;92         }93         //假如创建了新的对象,则继续往下执行94         synchronized (this) {95             createCount++;96             //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue97             mapValue = map.put(key, createdValue);98             if (mapValue != null) {99                 // There was a conflict so undo that last put
100                 //如果mapValue不为空,则撤销上一步的put操作。
101                 map.put(key, mapValue);
102             } else {
103                 //加入新创建的对象之后需要重新计算size大小
104                 size += safeSizeOf(key, createdValue);
105             }
106         }
107
108         if (mapValue != null) {
109             entryRemoved(false, key, createdValue, mapValue);
110             return mapValue;
111         } else {
112             //每次新加入对象都需要调用trimToSize方法看是否需要回收
113             trimToSize(maxSize);
114             return createdValue;
115         }
116     }
117
118     /
119       Caches {@code value} for {@code key}. The value is moved to the head of
120       the queue.
121      
122       @return the previous value mapped by {@code key}.
123      */
124     public final V put(K key, V value) {
125         if (key == null || value == null) {
126             throw new NullPointerException(“key == null || value == null”);
127         }
128
129         V previous;
130         synchronized (this) {
131             putCount++;
132             size += safeSizeOf(key, value);  //size加上预put对象的大小
133             previous = map.put(key, value);
134             if (previous != null) {
135                 //如果之前存在键为key的对象,则size应该减去原来对象的大小
136                 size -= safeSizeOf(key, previous);
137             }
138         }
139
140         if (previous != null) {
141             entryRemoved(false, key, previous, value);
142         }
143         //每次新加入对象都需要调用trimToSize方法看是否需要回收
144         trimToSize(maxSize);
145         return previous;
146     }
147
148     /
149       @param maxSize the maximum size of the cache before returning. May be -1
150           to evict even 0-sized elements.
151       此方法根据maxSize来调整内存cache的大小,如果maxSize传入-1,则清空缓存中的所有对象
152      /
153     private void trimToSize(int maxSize) {
154         while (true) {
155             K key;
156             V value;
157             synchronized (this) {
158                 if (size < 0 || (map.isEmpty() && size != 0)) {
159                     throw new IllegalStateException(getClass().getName()
160                             + “.sizeOf() is reporting inconsistent results!”);
161                 }
162                 //如果当前size小于maxSize或者map没有任何对象,则结束循环
163                 if (size <= maxSize || map.isEmpty()) {
164                     break;
165                 }
166                 //移除链表头部的元素,并进入下一次循环
167                 Map.Entry toEvict = map.entrySet().iterator().next();
168                 key = toEvict.getKey();
169                 value = toEvict.getValue();
170                 map.remove(key);
171                 size -= safeSizeOf(key, value);
172                 evictionCount++;  //回收次数+1
173             }
174
175             entryRemoved(true, key, value, null);
176         }
177     }
178
179     /
180       Removes the entry for {@code key} if it exists.
181      
182       @return the previous value mapped by {@code key}.
183       从内存缓存中根据key值移除某个对象并返回该对象
184      */
185     public final V remove(K key) {
186         if (key == null) {
187             throw new NullPointerException(“key == null”);
188         }
189
190         V previous;
191         synchronized (this) {
192             previous = map.remove(key);
193             if (previous != null) {
194                 size -= safeSizeOf(key, previous);
195             }
196         }
197
198         if (previous != null) {
199             entryRemoved(false, key, previous, null);
200         }
201
202         return previous;
203     }
204
205     /
206       Called for entries that have been evicted or removed. This method is
207       invoked when a value is evicted to make space, removed by a call to
208       {@link #remove}, or replaced by a call to {@link #put}. The default
209       implementation does nothing.
210      
211      The method is called without synchronization: other threads may
212       access the cache while this method is executing.
213      
214       @param evicted true if the entry is being removed to make space, false
215           if the removal was caused by a {@link #put} or {@link #remove}.
216       @param newValue the new value for {@code key}, if it exists. If non-null,
217           this removal was caused by a {@link #put}. Otherwise it was caused by
218           an eviction or a {@link #remove}.
219      /
220     protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
221
222     /
223       Called after a cache miss to compute a value for the corresponding key.
224       Returns the computed value or null if no value can be computed. The
225       default implementation returns null.
226      
227       The method is called without synchronization: other threads may
228       access the cache while this method is executing.
229      
230       If a value for {
@code key} exists in the cache when this method
231       returns, the created value will be released with {@link #entryRemoved}
232       and discarded. This can occur when multiple threads request the same key
233       at the same time (causing multiple values to be created), or when one
234       thread calls {@link #put} while another is creating a value for the same
235       key.
236      /
237     protected V create(K key) {
238         return null;
239     }
240
241     private int safeSizeOf(K key, V value) {
242         int result = sizeOf(key, value);
243         if (result < 0) {
244             throw new IllegalStateException(“Negative size: “ + key + “=” + value);
245         }
246         return result;
247     }
248
249     /
250       Returns the size of the entry for {@code key} and {@code value} in
251       user-defined units.  The default implementation returns 1 so that size
252       is the number of entries and max size is the maximum number of entries.
253      
254       An entry’s size must not change while it is in the cache.
255       用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小
256       xUtils中创建LruMemoryCache时就重写了sizeOf方法来计算bitmap的大小
257       mMemoryCache = new LruMemoryCache(globalConfig.getMemoryCacheSize()) {
258             @Override
259             protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {
260                 if (bitmap == null) return 0;
261                 return bitmap.getRowBytes()  bitmap.getHeight();
262             }
263         };
264      
265      /
266     protected int sizeOf(K key, V value) {
267         return 1;
268     }
269
270     /**
271       Clear the cache, calling {@link #entryRemoved} on each removed entry.
272       清空内存缓存
273      /
274     public final void evictAll() {
275         trimToSize(-1); // -1 will evict 0-sized elements
276     }
277
278     /
279       For caches that do not override {@link #sizeOf}, this returns the number
280       of entries in the cache. For all other caches, this returns the sum of
281       the sizes of the entries in this cache.
282      /
283     public synchronized final int size() {
284         return size;
285     }
286
287     /
288       For caches that do not override {@link #sizeOf}, this returns the maximum
289       number of entries in the cache. For all other caches, this returns the
290       maximum sum of the sizes of the entries in this cache.
291      /
292     public synchronized final int maxSize() {
293         return maxSize;
294     }
295
296     /
297       Returns the number of times {@link #get} returned a value.
298      /
299     public synchronized final int hitCount() {
300         return hitCount;
301     }
302
303     /
304       Returns the number of times {@link #get} returned null or required a new
305       value to be created.
306      /
307     public synchronized final int missCount() {
308         return missCount;
309     }
310
311     /**
312       Returns the number of times {@link #create(Object)} returned a value.
313      /
314     public synchronized final int createCount() {
315         return createCount;
316     }
317
318     /**
319       Returns the number of times {@link #put} was called.
320      /
321     public synchronized final int putCount() {
322         return putCount;
323     }
324
325     /**
326       Returns the number of values that have been evicted.
327      /
328     public synchronized final int evictionCount() {
329         return evictionCount;
330     }
331
332     /**
333       Returns a copy of the current contents of the cache, ordered from least
334       recently accessed to most recently accessed.
335      /
336     public synchronized final Map snapshot() {
337         return new LinkedHashMap(map);
338     }
339
340     @Override public synchronized final String toString() {
341         int accesses = hitCount + missCount;
342         int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
343         return String.format(“LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]”,
344                 maxSize, hitCount, missCount, hitPercent);
345     }
346 }
看完代码是不是觉得内存缓存的实现其实很简单?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543345.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装gem_Python安装第三方库及常见问题处理方法汇总

源码安装Python第三方库几乎都可以在github或者 pypi上找到源码。源码包格式大概有zip 、 tar.zip、 tar.bz2。解压这些包&#xff0c;进入解压好的文件夹&#xff0c;通常会有一个setup.py的文件。打开命令行&#xff0c;进入该文件夹。运行以下命令&#xff0c;就能把这个第三…

No style sheet with given id found错误

在chrome中打开html页面&#xff0c;报错No style sheet with given id found&#xff0c;解决方如下 转载于:https://www.cnblogs.com/lcyuhe/p/5439832.html

查询2021高考成绩位次,云南一分一段表查询2021-云南高考位次查询(文科、理科)...

云南高考一分一段表是同学们在填报高考志愿时的重要参考资料之一。根据一分一段表&#xff0c;大家不仅可以清楚地了解自己的高考成绩在全省的排名&#xff0c;还可以结合心仪的大学近3年在云南的录取位次变化&#xff0c;判断出自己被录取的概率大概是多少。根据考试院公布的数…

c语言goto语句用法_C语言32个关键字9种控制语句34种运算符整理

相信这么努力的你 已经星标了我 老九学堂 你身边的IT导师32个关键字&#xff0c;如下表所示&#xff1a;关键字说明auto声明自动变量short声明短整型变量或函数int声明整型变量或函数long声明长整型变量或函数float声明浮点型变量或函数double声明双精度变量或函数char声明字符…

人生苦短python作伴_“人生苦短,我用Python”

经过人机大战&#xff0c;AlphaGo一战成名&#xff0c;Python也确立了人工智能时代头牌开发语言的地位&#xff0c;国家相关教育部门更是把它纳入到了传统教育体系中。对于有跨界转型需求的小伙伴来说&#xff0c;get这门技能是个不错的切入点。这一篇文章&#xff0c;就来和大…

《Java编程思想》第四版读书笔记 第四章

2019独角兽企业重金招聘Python工程师标准>>> 4.3 逗号操作费仅用于for循环控制表达式的初始化部分和步进控制部分。初始化部分用于定义任意多个具有相同类型的变量&#xff1a; for(int i 1, j i 10; i <5; i, j i* 2) for循环控制表达式可以使用对象而不仅仅…

access 导入txt 找不到可安装的isam_由浅入深:Python 中如何实现自动导入缺失的库?...

? “Python猫” &#xff0c;一个值得加星标的公众号在写 Python 项目的时候&#xff0c;我们可能经常会遇到导入模块失败的错误&#xff1a;ImportError: No module named xxx或者ModuleNotFoundError: No module named xxx。导入失败问题&#xff0c;通常分为两种&#xff1…

英语计算机单词mp3,[听单词] 计算机专业英语词汇音频51,计算机英语单词MP3

calcd adj.已计算的interrupt data block&#xff0c;IDB 中断资料块ZBR Zero BRanch 零转移〖指令〗characteristic signal 特性信号processor cycle time 处理机周期时间plug-and-go integration 即插即拨集成relocating linkage loader n.浮动装入程序dispersiveness n.色散…

怎么判断私网地址_判断本机IP地址是公网地址还是私网地址

对于ip是否是公网ip&#xff0c;网上已经有很多文章进行了描述。但我每次都记不太住&#xff0c;总要查找一下才又清楚。因此决定在这里记录下来&#xff0c;方便以后查询&#xff1a;)ip地址分为五类。E类为保留为今后使用&#xff0c;D类为组播地址。用于主机网络地址的就是A…

pycharm中python版本_在 Pycharm(2019,.3)里配置 Anaconda3 的 Python 版本

先说配置方法&#xff1a; 一 在已有项目中配置&#xff1a; Virtualenv Environment 里选择 Existing environment 在 interpreter 输入或跟着路径找&#xff1a; /Users/***/opt/anaconda3/python.app/Contents/MacOS/python ***是用户名&#xff0c; 我这个应该默认的安装路…

Struts2 ActionWildcard(通配符配置)约定优于配置

新建web project&#xff1a;struts2_0500_actionwildcard Build Path 项目图&#xff1a; src&#xff1a; StudentAction.java TeacherAction.java struts.xml WebRoot: index.jsp Student_add.jsp Student_delete.jsp Student_edit.jsp Student_find.jsp …

html制作相册影集,用影集制作系统 轻松制作绚丽电子相册

“结庐在人境&#xff0c;而无车马喧。问君何能尔&#xff0c;心远地自偏。采菊东篱下&#xff0c;悠然见南山。山气日夕佳&#xff0c;飞鸟相与还。此中有真意&#xff0c;欲辨已忘言……”陶渊明先生以冲淡清远之笔&#xff0c;为我们描述了田园生活和虚里风光&#xff0c;利…

python3 gui协程_Python3进阶-协程

协程简介又称为微进程&#xff0c;纤程。非抢占式使用资源。优点优点1&#xff1a;协程极高的执行效率。因为子程序切换不是纤程切换&#xff0c;而是由程序自身控制&#xff0c;因此&#xff0c;没有纤程切换的开销&#xff0c;和多线程对比&#xff0c;纤程数量越多&#xff…

计算机主机ip地址题,计算机网络IP地址练习习题

IP地址习题1、192.168.1.1代表的是()地址。A、A类地址B、B类地址C、C类地址D、D类地址2、224.0.0.5代表的是()地址。A、主机地址B、网络地址C、组播地址D、广播地址3、192.168.1.255代表的是()地址。A、主机地址B、网络地址C、组播地址D、广播地址4、对于一个没有经过子网划分的…

实验一 线性表的顺序存储与实现_数据结构篇之单链表的创建以及实现

上次咱们已经一起分享了线性表的顺序存储的基本创建以及一些运算方法的实现&#xff0c;那么这次咱们主要来研究线性表的链式存储&#xff0c;俗称单链表&#xff0c;咱们知道顺序表在建立的时候要注意需要建立一块连续的空间&#xff0c;所以需要使用数组在内存中开辟空间&…

多线程基础(二)pthread的了解

IOS中多线程的实现方案 了解NSOperation&#xff08;代码&#xff09; 所有的方法都是pthread开头的 然后再搞一条线程 pthread_create方法有返回值&#xff0c;作用&#xff1a;判断线程创建是否成功&#xff1f; 常用的方法&#xff1a; 退出的方法: 关于pthread代码几乎不用…

计算机专业技能高考试题素材,计算机技能高考模拟试题

计算机技能高考模拟试题(2016年湖北)《组装与维护模块》本套试卷选取3套高考模拟卷&#xff0c;自制而成&#xff0c;请同学们45分钟完成全部答题(2018-06-12)(标准&#xff1a;总分80分&#xff0c;48分及格&#xff0c;64分良好&#xff0c;64以上优秀)Ⅲ、计算机组装与维修(…

python爬虫requests源码链家_Python爬虫之---爬链家

一个简单的实例&#xff0c;可以采用。做的demo。 #!/usr/bin/python # -*- coding: utf-8 -*- # Time : 2020/6/4 15:55 # Author : Xuegod Teacher For # File : 01_get_price_test.py # Software: PyCharm第一步&#xff1a;下载所有的html 第二步&#xff1a;利用xpath爬取…

iOS之UI--使用SWRevealViewController实现侧边菜单功能详解实例

使用SWRevealViewController实现侧边菜单功能详解 下面通过两种方法详解SWRevealViewController实现侧边菜单功能&#xff1a; 1.使用StoryBoard实现 2.纯代码实现 前言&#xff1a;手机屏幕始终有限&#xff0c;如何在有限的展示空间提供便捷的导航入口呢&#xff1f;Facebook…

计算机网络基础大学教材,《计算机网络技术及应用——大学计算机基础教育规划教材》低价购书_教材教辅考试_孔网...

本书在介绍数据通信概念的基础上&#xff0c;分局域网、广域网和因特网三个模块详细讲述了计算机网络的基础知识&#xff0c;还介绍了网络安全和网络管理方面的实用技术。本书的内容是兼顾基础&#xff0c;突出应用。本书取材新颖&#xff0c;概念清晰&#xff0c;可作为理工科…