内容简介:下面,将详细介绍
前言
Android LrhCache
1. 简介
下面,将详细介绍 LrhCache
算法
2. LrhCache算法
3. 实现原理
-
LrhCache
算法的算法核心 =LRU
算法 +LinkedHashMap
数据结构 - 下面,我将先介绍
LRU
算法 和LinkedHashMap
数据结构,最后再介绍LrhCache
算法
3.1 LRU 算法
- 定义:
Least Recently Used
,即 近期最少使用算法 - 算法原理:当缓存满时,优先淘汰 近期最少使用的缓存对象
采用
LRU
算法的缓存类型:内存缓存(LrhCache
) 、 硬盘缓存(DisLruCache
)
3.2 LinkedHashMap 介绍
- 数据结构 = 数组 +单链表 + 双向链表
- 其中,双向链表 实现了 存储顺序 = 访问顺序 / 插入顺序
- 使得
LinkedHashMap
中的<key,value>
对 按照一定顺序进行排列 - 通过 构造函数 指定LinkedHashMap中双向链表的结构是访问顺序 or 插入顺序
- 使得
/** * LinkedHashMap 构造函数 * 参数accessOrder = true时,存储顺序(遍历顺序) = 外部访问顺序;为false时,存储顺序(遍历顺序) = 插入顺序 **/ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
- 实例演示
当 accessOrder
参数设置为 true
时,存储顺序(遍历顺序) = 外部访问顺序
/** * 实例演示 **/ // 1. accessOrder参数设置为true时 LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true); // 2. 插入数据 map.put(0, 0); map.put(1, 1); map.put(2, 2); map.put(3, 3); map.put(4, 4); map.put(5, 5); map.put(6, 6); // 3. 访问数据 map.get(1); map.get(2); // 遍历获取LinkedHashMap内的数据 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } /** * 测试结果 **/ 0:0 3:3 4:4 5:5 6:6 1:1 2:2 // 即实现了 最近访问的对象 作为 最后输出 // 该逻辑 = LrhCache缓存算法思想 // 可见LruCache的实现是利用了LinkedHashMap数据结构的实现原理 // 请看LruCache的构造方法 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时传入true。即采用了存储顺序 = 外界访问顺序 = 最近访问的对象 作为 最后输出 }
3.3 LrhCache 算法原理
- 示意图
4. 使用流程
/** * 使用流程(以加载图片为例) **/ private LruCache<String, Bitmap> mMemoryCache; // 1. 获得虚拟机能提供的最大内存 // 注:超过该大小会抛出OutOfMemory的异常 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 2. 设置LruCache缓存的大小 = 一般为当前进程可用容量的1/8 // 注:单位 = Kb // 设置准则 // a. 还剩余多少内存给你的activity或应用使用 // b. 屏幕上需要一次性显示多少张图片和多少图片在等待显示 // c. 手机的大小和密度是多少(密度越高的设备需要越大的 缓存) // d. 图片的尺寸(决定了所占用的内存大小) // e. 图片的访问频率(频率高的在内存中一直保存) // f. 保存图片的质量(不同像素的在不同情况下显示) final int cacheSize = maxMemory / 8; // 3. 重写sizeOf方法:计算缓存对象的大小(此处的缓存对象 = 图片) mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; // 此处返回的是缓存对象的缓存大小(单位 = Kb) ,而不是item的个数 // 注:缓存的总容量和每个缓存对象的大小所用单位要一致 // 此处除1024是为了让缓存对象的大小单位 = Kb } }; // 4. 将需缓存的图片 加入到缓存 mMemoryCache.put(key, bitmap); // 5. 当 ImageView 加载图片时,会先在LruCache中看有没有缓存该图片:若有,则直接获取 mMemoryCache.get(key);
5. 实例讲解
- 本实例以缓存图片为实例讲解
- 具体代码
请看注释
MainActivity.java
public class MainActivity extends AppCompatActivity { public static final String TAG = "carsonTest:"; private LruCache<String, Bitmap> mMemoryCache; private ImageView mImageView; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1. 获得虚拟机能提供的最大内存 // 注:超过该大小会抛出OutOfMemory的异常 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 2. 设置LruCache缓存的大小 = 一般为当前进程可用容量的1/8 // 注:单位 = Kb final int cacheSize = maxMemory / 8; // 3. 重写sizeOf方法:计算缓存对象的大小(此处的缓存对象 = 图片) mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; // 此处返回的是缓存对象的缓存大小(单位 = Kb) ,而不是item的个数 // 注:缓存的总容量和每个缓存对象的大小所用单位要一致 // 此处除1024是为了让缓存对象的大小单位 = Kb } }; // 4. 点击按钮,则加载图片 mImageView = (ImageView)findViewById(R.id.image); button = (Button)findViewById(R.id.btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 加载图片 ->>分析1 loadBitmap("test",mImageView); } }); } /** * 分析1:加载图片 * 加载前,先从内存缓存中读取;若无,则再从数据源中读取 **/ public void loadBitmap(String key, ImageView imageView) { // 读取图片前,先从内存缓存中读取:即看内存缓存中是否缓存了该图片 // 1. 若有缓存,则直接从内存中加载 Bitmap bitmap = mMemoryCache.get(key); if (bitmap != null) { mImageView.setImageBitmap(bitmap); Log.d(TAG, "从缓存中加载图片 "); // 2. 若无缓存,则从数据源加载(此处选择在本地加载) & 添加到缓存 } else { Log.d(TAG, "从数据源(本地)中加载: "); // 2.1 从数据源加载 mImageView.setImageResource(R.drawable.test1); // 2.1 添加到缓存 // 注:在添加到缓存前,需先将资源文件构造成1个BitMap对象(含设置大小) Resources res = getResources(); Bitmap bm = BitmapFactory.decodeResource(res, R.drawable.test1); // 获得图片的宽高 int width = bm.getWidth(); int height = bm.getHeight(); // 设置想要的大小 int newWidth = 80; int newHeight = 80; // 计算缩放比例 float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // 取得想要缩放的matrix参数 Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // 构造成1个新的BitMap对象 Bitmap bitmap_s = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true); // 添加到缓存 if (mMemoryCache.get(key) == null) { mMemoryCache.put(key, bitmap_s); Log.d(TAG, "添加到缓存: " + (mMemoryCache.get(key))); } } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:focusableInTouchMode="true" android:orientation="vertical"> <ImageView android:id="@+id/image" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center" /> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="点击加载" android:layout_gravity="center" /> </LinearLayout>
-
测试结果
第1次点击加载图片时,由于无缓存则从本地加载
第2次(以后)点击加载图片时,由于有缓存,所以直接从缓存中读取
6. 源码分析
此处主要分析 写入缓存 & 获取缓存 ,即 put()
、 get()
6.1 添加缓存:put()
- 源码分析
/** * 使用函数(以加载图片为例) **/ mMemoryCache.put(key,bitmap); /** * 源码分析 **/ public final V put(K key, V value) { // 1. 判断 key 与 value是否为空 // 若二者之一味空,否则抛出异常 if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; synchronized (this) { // 2. 插入的缓存对象值加1 putCount++; // 3. 增加已有缓存的大小 size += safeSizeOf(key, value); // 4. 向map中加入缓存对象 previous = map.put(key, value); // 5. 若已有缓存对象,则缓存大小恢复到之前 if (previous != null) { size -= safeSizeOf(key, previous); } } // 6. 资源回收(移除旧缓存时会被调用) // entryRemoved()是个空方法,可自行实现 if (previous != null) { entryRemoved(false, key, previous, value); } // 7. 添加缓存对象后,调用需判断缓存是否已满 // 若满了就删除近期最少使用的对象-->分析2 trimToSize(maxSize); return previous; } /** * 分析1:trimToSize(maxSize) * 原理:不断删除LinkedHashMap中队尾的元素,即近期最少访问的元素,直到缓存大小 < 最大值 **/ public void trimToSize(int maxSize) { //死循环 while (true) { K key; V value; synchronized (this) { // 判断1:若 map为空 & 缓存size ≠ 0 或 缓存size < 0,则抛出异常 if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } // 判断2:若缓存大小size < 最大缓存 或 map为空,则不需删除缓存对象,跳出循环 if (size <= maxSize || map.isEmpty()) { break; } // 开始删除缓存对象 // 使用迭代器获取第1个对象,即队尾的元素 = 近期最少访问的元素 Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); // 删除该对象 & 更新缓存大小 map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
至此,关于添加缓存: put()
的源码分析完毕。
6.2 获取缓存:get()
- 作用:获取缓存 & 更新队列
get()
- 示意图如下
上述更新过程是在 get()
中完成
- 源码分析
/** * 使用函数(以加载图片为例) **/ mMemoryCache.get(key); /** * 源码分析 **/ public final V get(K key) { // 1. 判断输入的合法性:若key为空,则抛出异常 if (key == null) { throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { // 2. 获取对应的缓存对象 & 将访问的元素 更新到 队列头部->>分析3 mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } /** * 分析1:map.get(key) * 实际上是 LinkedHashMap.get() **/ public V get(Object key) { // 1. 获取对应的缓存对象 LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key); if (e == null) return null; // 2. 将访问的元素更新到队列头部 ->>分析4 e.recordAccess(this); return e.value; } /** * 分析2:recordAccess() **/ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; // 1. 判断LinkedHashMap存储顺序是否按访问 排序 排序:根据构造函数传入的参数accessOrder判断 if (lm.accessOrder) { lm.modCount++; // 2. 删除此元素 remove(); // 3. 将此元素移动到队列的头部 addBefore(lm.header); } }
至此,关于获取缓存: get()
的源码分析完毕。
7. 总结
本文全面讲解了内存缓存的相关知识,含 LrhCache
算法、原理等,下面是部分总结
- 原理
- 示意图
- 源码流程
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 缓存的一些问题和一些加密算法【缓存问题】
- 算法 - 06 | 链表(上):如何实现LRU缓存淘汰算法?
- 数据结构与算法 | 如何实现LRU缓存淘汰算法
- golang实现LRU缓存淘汰算法
- 知多一点 LRU 缓存算法
- 聊聊缓存淘汰算法:LRU 实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据挖掘十大算法
(美)吴信东(Xindong Wu)、(美),库玛尔 ,(Vipin Kumar) / 李文波、吴素研 / 清华大学出版社 / 2013-5 / 39.00元
《世界著名计算机教材精选:数据挖掘十大算法》详细介绍了在实际中用途最广、影响最大的十种数据挖掘算法,这十种算法是数据挖掘领域的顶级专家进行投票筛选的,覆盖了分类、聚类、统计学习、关联分析和链接分析等重要的数据挖掘研究和发展主题。《世界著名计算机教材精选:数据挖掘十大算法》对每一种算法都进行了多个角度的深入剖析,包括算法历史、算法过程、算法特性、软件实现、前沿发展等,此外,在每章最后还给出了丰富的习......一起来看看 《数据挖掘十大算法》 这本书的介绍吧!