LruCache是Android的一個(gè)內(nèi)部類,提供了基于內(nèi)存實(shí)現(xiàn)的緩存
雙向鏈表
LinkedHashMap 是 key 鍵有序的 HashMap 的一種實(shí)現(xiàn)。它除了使用哈希表這個(gè)數(shù)據(jù)結(jié)構(gòu),使用雙向鏈表來保證 key 的順序

雙向鏈表算是個(gè)很常見的數(shù)據(jù)結(jié)構(gòu),上圖中的頭節(jié)點(diǎn)的 prev、尾節(jié)點(diǎn)的 next 指向 null,雙向鏈表還有一種變種,見下圖

LinkedHashMap 就是采用的這種方式
源碼
先看源碼定義
public class LinkedHashMap<K, V> extends HashMap<K, V> {
其是繼承 HashMap,那么就會(huì)擁有 HashMap 的大部分特色,例如允許 null 鍵和 null 值,默認(rèn)容量是16,裝載因子是 0.75,非線程安全等等咯。另外,根據(jù)名字猜測(cè),也會(huì)帶有 LinkList 的特點(diǎn)。下面分析一下
定義的變量
/**
* A dummy entry in the circular linked list of entries in the map.
* The first real entry is header.nxt, and the last is header.prv.
* If the map is empty, header.nxt == header && header.prv == header.
*/
transient LinkedEntry<K, V> header; // 內(nèi)部雙向鏈表的頭結(jié)點(diǎn)
首先,請(qǐng)先看LinkedEntry這個(gè)數(shù)據(jù)結(jié)構(gòu)
LinkedEntry
/**
* LinkedEntry adds nxt/prv double-links to plain HashMapEntry.
*/
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
LinkedEntry<K, V> nxt;
LinkedEntry<K, V> prv;
/** Create the header entry */
LinkedEntry() {
super(null, null, 0, null);
nxt = prv = this;
}
/** Create a normal entry */
LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
super(key, value, hash, next);
this.nxt = nxt;
this.prv = prv;
}
}
我們看到 LinkedEntry 繼承了 HashMapEntry,并且在其基礎(chǔ)上擴(kuò)展了兩個(gè)屬性:nxt 表示鏈表的下一個(gè)元素,prv表示鏈表的前一個(gè)元素。
而這個(gè) header,大家看注釋:The first real entry is header.nxt, and the last is header.prv.If the map is empty, header.nxt == header && header.prv == header.
第一個(gè)真正的 entry 是 header 的下一個(gè),最后一個(gè) entry 是 header 的前一個(gè);如果為 null,則他的前一個(gè)和后一個(gè)都等于 header,因?yàn)樗莻€(gè)環(huán)形鏈表,所以 header 的下一個(gè)也是最先加入的,header 的前一個(gè)是最后一個(gè)加入的,下面的圖可能大家就明了了
/**
* True if access ordered, false if insertion ordered.
*/
private final boolean accessOrder;// 是否按照訪問順序
accessOrder 代表的是是否按照訪問順序,可以分為:按插入順序的鏈表,和按訪問順序的鏈表。true 表示按照訪問順序迭代,false 時(shí)表示按照插入順序。而如果要實(shí)現(xiàn) LRUCache 的 LRU 算法,就需要選擇 true。
構(gòu)造方法
/**
* Constructs a new empty {@code LinkedHashMap} instance.
*/
public LinkedHashMap() {
init();
accessOrder = false;// 默認(rèn)為false,也就是插入順序
}
/**
* Constructs a new {@code LinkedHashMap} instance with the specified
* capacity.
*
* @param initialCapacity
* the initial capacity of this map.
* @throws IllegalArgumentException
* when the capacity is less than zero.
*/
public LinkedHashMap(int initialCapacity) {//initialCapacity是初始化空間大小
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new {@code LinkedHashMap} instance with the specified
* capacity and load factor.
*
* @param initialCapacity
* the initial capacity of this map.
* @param loadFactor
* the initial load factor.
* @throws IllegalArgumentException
* when the capacity is less than zero or the load factor is
* less or equal to zero.
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {// loadFactor是加載因子,當(dāng)他的size大于initialCapacity*loadFactor的時(shí)候就會(huì)擴(kuò)容,他的默認(rèn)值是0.75
this(initialCapacity, loadFactor, false);
}
/**
* Constructs a new {@code LinkedHashMap} instance with the specified
* capacity, load factor and a flag specifying the ordering behavior.
*
* @param initialCapacity
* the initial capacity of this hash map.
* @param loadFactor
* the initial load factor.
* @param accessOrder
* {@code true} if the ordering should be done based on the last
* access (from least-recently accessed to most-recently
* accessed), and {@code false} if the ordering should be the
* order in which the entries were inserted.
* @throws IllegalArgumentException
* when the capacity is less than zero or the load factor is
* less or equal to zero.
*/
public LinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
init();
this.accessOrder = accessOrder;
}
/**
* Constructs a new {@code LinkedHashMap} instance containing the mappings
* from the specified map. The order of the elements is preserved.
*
* @param map
* the mappings to add.
*/
public LinkedHashMap(Map<? extends K, ? extends V> map) {
this(capacityForInitSize(map.size()));
constructorPutAll(map);
}
下面看看init方法
init()
@Override void init() {
header = new LinkedEntry<K, V>();
}
這個(gè)方法初始化了一個(gè)header,也就是雙向環(huán)形鏈表的頭,并沒有存放任何的數(shù)據(jù)。
在HashMap的源碼中, init方法是一個(gè)空方法
/**
* This method is called from the pseudo-constructors (clone and readObject)
* prior to invoking constructorPut/constructorPutAll, which invoke the
* overridden constructorNewEntry method. Normally it is a VERY bad idea to
* invoke an overridden method from a pseudo-constructor (Effective Java
* Item 17). In this case it is unavoidable, and the init method provides a
* workaround.
*/
void init() { }
LinkedHashMap 繼承 HashMap 后重寫了該方法。
初始變量和構(gòu)造方法講完后,下面按源文件順序看看它定義的一些方法
eldest()
/**
* Returns the eldest entry in the map, or {@code null} if the map is empty.
* @hide
*/
public Entry<K, V> eldest() {
LinkedEntry<K, V> eldest = header.nxt;
return eldest != header ? eldest : null;
}
這個(gè)方法看注釋就明了:得到最老的元素,也是最先存放的元素或者如果 map 為空,就返回null。通過上面分析。我們知道最先存放的元素就是 header 的nxt。
addNewEntry()
/**
* Evicts eldest entry if instructed, creates a new entry and links it in
* as head of linked list. This method should call constructorNewEntry
* (instead of duplicating code) if the performance of your VM permits.
*
* <p>It may seem strange that this method is tasked with adding the entry
* to the hash table (which is properly the province of our superclass).
* The alternative of passing the "next" link in to this method and
* returning the newly created element does not work! If we remove an
* (eldest) entry that happens to be the first entry in the same bucket
* as the newly created entry, the "next" link would become invalid, and
* the resulting hash table corrupt.
*/
@Override void addNewEntry(K key, V value, int hash, int index) {
LinkedEntry<K, V> header = this.header;
// Remove eldest entry if instructed to do so.
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}
// Create new entry, link it on to list, and put it into table
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
key, value, hash, table[index], header, oldTail);
table[index] = oldTail.nxt = header.prv = newTail;
}
這個(gè)方法比 HashMap 的 addNewEntry 代碼行數(shù)要多了。看代碼可知,該方法是加入一個(gè)新的 entry,首先是找到 header,然后根據(jù)條件判斷是否移除最老元素,默認(rèn)的 removeEldestEntry() 方法返回 false
removeEldestEntry
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return false;
}
如果其返回了 true,就會(huì)移除最老元素。
接下來,可以看到,首先是新建了一個(gè)新的 LinkedEntry,然后在加入到 table 中,最后一行中header.prv = newTail,這回大家可能明白了 header 的前一個(gè)元素就是新元素,也是最后一個(gè)加入的。
還有一個(gè) addNewEntryForNullKey 方法,向 HashMap 看齊,是加入 null 的 entry
addNewEntryForNullKey
@Override void addNewEntryForNullKey(V value) {
LinkedEntry<K, V> header = this.header;
// Remove eldest entry if instructed to do so.
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}
// Create new entry, link it on to list, and put it into table
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
null, value, 0, null, header, oldTail);
entryForNullKey = oldTail.nxt = header.prv = newTail;
}
差別就是最后的一句話
繼續(xù)看constructorNewEntry()方法
constructorNewEntry()
/**
* As above, but without eviction.
*/
@Override HashMapEntry<K, V> constructorNewEntry(
K key, V value, int hash, HashMapEntry<K, V> next) {
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail
= new LinkedEntry<K,V>(key, value, hash, next, header, oldTail);
return oldTail.nxt = header.prv = newTail;
}
注釋很明了:只不過他沒有調(diào)用移除方法
下面就是get()方法
get()
/**
* Returns the value of the mapping with the specified key.
*
* @param key
* the key.
* @return the value of the mapping with the specified key, or {@code null}
* if no mapping for the specified key is found.
*/
@Override public V get(Object key) {
/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)//這個(gè)方法和HashMap的get方法差不多,只不過他多了一個(gè)判斷,因?yàn)長(zhǎng)inkedHashMap多了一個(gè)鏈表的維護(hù),所以他要判斷是否是按照訪問順序來維護(hù),
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}
該方法復(fù)寫了 HashMap 的 get 方法,并加入了自己的特色。
如果accessOrder為true(安裝訪問順序維護(hù)),就會(huì)調(diào)用下面的方法
makeTail()
/**
* Relinks the given entry to the tail of the list. Under access ordering,
* this method is invoked whenever the value of a pre-existing entry is
* read by Map.get or modified by Map.put.
*/
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv;
// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}
這個(gè)方法是實(shí)現(xiàn) Lru 算法的關(guān)鍵,將元素插入到頭表前一個(gè)元素(離頭表最近的元素,也是最新的元素)。
頭兩行代碼的意思就是把元素 e 從雙向鏈表中刪除,然后將 e 添加到 header 的前面(就是 tail),相當(dāng)于把 e 又新添加到了雙向鏈表中,最后的幾行代碼就是確保整個(gè)雙向鏈表不能斷掉。
get 方法的剩余部分類似 HashMap 的 get 方法,只不過在循環(huán)查找過程中,會(huì)再次判斷訪問類型。
preModify()
@Override void preModify(HashMapEntry<K, V> e) {
if (accessOrder) {
makeTail((LinkedEntry<K, V>) e);
}
}
該方法被 LinkedHashMap 重寫,還記得 HashMap 的 put 方法(LinkedHashMap 并沒有重寫該方法,所以還是用的 HashMap 的 put 方法)嗎,那里面涉及到了該方法。如果 accessOrder 為 true ,則 LinkedHashMap 的 put 效果如下
看上面這張圖咯,有沒有啥問題呢?
如果 accessOrder 為 true ,那么,在 put<k1,v1> 時(shí),就不應(yīng)該放在 header.nxt,而應(yīng)該是 entry_1 和 entry_0 顛倒一下就正確了。(為的就是保證每次 header 的 tail 是最近用到的,而 header.nxt 就是最久未使用的)
最新的元素總是屬于 header 的 pre (也就是 the last)
postRemove()
@Override void postRemove(HashMapEntry<K, V> e) {
LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
le.prv.nxt = le.nxt;
le.nxt.prv = le.prv;
le.nxt = le.prv = null; // Help the GC (for performance)
}
該方法就是去除結(jié)點(diǎn)的操作
containsValue()
/**
* This override is done for LinkedHashMap performance: iteration is cheaper
* via LinkedHashMap nxt links.
*/
@Override public boolean containsValue(Object value) {
if (value == null) {
for (LinkedEntry<K, V> header = this.header, e = header.nxt;
e != header; e = e.nxt) {
if (e.value == null) {
return true;
}
}
return false;
}
// value is non-null
for (LinkedEntry<K, V> header = this.header, e = header.nxt;
e != header; e = e.nxt) {//迭代鏈表
if (value.equals(e.value)) {
return true;
}
}
return false;
}
LinkedHashMap 的 containsValue 方法是遍歷雙向鏈表,從鏈表的 header 開始查找,因?yàn)槭黔h(huán)形的,如果查找一圈還沒找到則返回false。 containsValue 從鏈表中查詢,而 HashMap 從 table 數(shù)組中查詢,進(jìn)行該操作時(shí),沒有 HashMap 快(數(shù)組比鏈表迭代快)。
clear()
public void clear() {
super.clear();
// Clear all links to help GC
LinkedEntry<K, V> header = this.header;
for (LinkedEntry<K, V> e = header.nxt; e != header; ) {
LinkedEntry<K, V> nxt = e.nxt;
e.nxt = e.prv = null;
e = nxt;
}
header.nxt = header.prv = header;
}
清空鏈表咯
剩下的就是迭代器類了,就不貼了,感興趣的可以自己看源碼,注意:HashMap:是迭代數(shù)組,LinkedHashMap 迭代鏈表。