一日一學_LruCache(源碼解讀)

在學習LruCache源碼之前,我們有必要簡單了解LinkedHashMap(使用):

LinkedHashMap

LinkedHashMap 內部維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。(多個線程同時訪問鏈接的哈希映射,其中至少一個線程從結構上修改了該映射,必須保持外部同步。)

  • LinkedHashMap元素的順序:
  1. 插入順序的鏈表;(默認插入順序)
  2. 訪問順序(調用 get 方法)的鏈表。(調用get方法后,會將這次元素移到鏈表尾)

定義一般都是很難理解的,來倆個demo就明白了


其實很簡答
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
    map.put("A","java");
    map.put("B", "golang");
    map.put("C", "c#");
    map.put("D", "php");

    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry) iter.next();
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }

運行結果

A=java
B=golang
C=c#
D=php

log可以看出輸出順序按照插入順序的!
訪問順序進行排序么?下面我們繼續

 LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
    map.put("A","java");
    map.put("B", "golang");
    map.put("C", "c#");
    map.put("D", "php");
    map.get("A");
    map.get("D");

    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry) iter.next();
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }

構造函數不相同,看一下結果


B=golang
C=c#
A=java
D=php


LruCache源碼分析

先對get方法進行分析

public final V get(K key) {
        // key為空會拋異常
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            //若key不為空則命中次數hitCount加1并return這個value,
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            //否則missCount加1
            missCount++;
        }
        //根據key嘗試創建value,如果創建返回的createdValue是null, 直接返回null
      V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            //若createdValue不為null,則把createdValue放回map
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }
        //存在舊值則返回舊值,否則返回這個createdValue。
        //這一步是不是很迷糊啊
        /*
              map.put("A","java");
              String  Astr =  map.put("A","c#");
              key一致的情況下(Astr上一個舊值是java)
        */
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

再看看我們put方法

 public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            //put方法將鍵值對放入map
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        //重新計算大小,刪除訪問次數最少的元素。
        trimToSize(maxSize);
        return previous;
    }

trimToSize會一直嘗試刪除隊首元素(訪問次數最少的元素),直到size不超過最大容量。

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();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }


    //這個方法要特別注意,如果看了我Rxhttp緩存那塊代碼,
    //應該知道我重寫這個方法,測量對象內存大小(總和)與maxsize進行比較,
    //才能正確的進行內存回收
   protected int sizeOf(K key, V value) { 
          return 1; 
    }

Rxhttp部分代碼


 mCache = new LruCache<String, Serializable>(cacheSize) {
            @Override
            protected int sizeOf(String key, Serializable value) {
               return  calcSize(value);
            }
        };

-------------------
  /**
     * 測量對象內存占用大小(實現Serializable)
     * 這么測量對象內存是錯誤的(有誤差)
     */
   
 private static int calcSize(Serializable o) {
        int ret = 0;
        class DumbOutputStream extends OutputStream {
            int count = 0;
            public void write(int b) throws IOException {
                count++; // 只計數,不產生字節轉移
            }
        }
        DumbOutputStream buf = new DumbOutputStream();
        ObjectOutputStream os = null;
        try {
            os = new ObjectOutputStream(buf);
            os.writeObject(o);
            ret = buf.count;
        } catch (IOException e) {
            // No need handle this exception
            e.printStackTrace();
            ret = -1;
        } finally {
            try {
                os.close();
            } catch (Exception e) {
            }
        }
        return ret;
    }

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容