目錄
1. 簡介
下面,將詳細介紹 LrhCache算法
2. LruCache算法
3. 實現原理
- LrhCache算法的算法核心 = LRU 算法 + LinkedHashMap數據結構
- 下面,我將先介紹LRU 算法 和 LinkedHashMap數據結構,最后再介紹LrhCache算法
3.1 LRU 算法
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:03:34:45:56:61:12: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;@Overrideprotected 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// 注:單位 = Kbfinal int cacheSize = maxMemory / 8;// 3. 重寫sizeOf方法:計算緩存對象的大小(此處的緩存對象 = 圖片)mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected 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() {@Overridepublic void onClick(View view) {// 加載圖片 ->>分析1loadBitmap(
"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"><ImageViewandroid:id=
"@+id/image"android:layout_width=
"200dp"android:layout_height=
"200dp"android:layout_gravity=
"center"/><Buttonandroid: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. 插入的緩存對象值加1putCount++; // 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. 添加緩存對象后,調用需判斷緩存是否已滿// 若滿了就刪除近期最少使用的對象-->分析2trimToSize(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() 獲取緩存對象時,就代表訪問了1次該元素
- 訪問后將會更新隊列,使得整個隊列是按照 訪問順序 排列
- 示意圖如下
上述更新過程是在 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. 獲取對應的緩存對象 & 將訪問的元素 更新到 隊列頭部->>分析3mapValue = 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. 將訪問的元素更新到隊列頭部 ->>分析4e.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算法、原理等,下面是部分總結
轉載于:https://juejin.im/post/5bfe02426fb9a049ec6ac4c6
總結
以上是生活随笔為你收集整理的转:LruCache算法的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。