在學習LruCache源碼之前,我們有必要簡單了解LinkedHashMap(使用):
LinkedHashMap
LinkedHashMap 內部維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。(多個線程同時訪問鏈接的哈希映射,其中至少一個線程從結構上修改了該映射,必須保持外部同步。)
- LinkedHashMap元素的順序:
- 插入順序的鏈表;(默認插入順序)
- 訪問順序(調用 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;
}