基於jdk 1.6 源碼分析
1.結構
繼承了hashmap,重寫了部分方法來實現有序
2.有序原理
首先看hashmap 的數據結構
每個元素只跟在相同位置的元素有關係
linkedhashmap 的數據結構
entry 元素 除了有next 指針,還有Before,after 指針 指定前後結點的關係。
新增了一個header 元素,作爲entry 雙向鏈表的頭結點
3.源碼分析
要想實現有序,無非是在保存或者遍歷元素的時候更新元素在鏈表中的位置。
3.1 首先看構造方法
public LinkedHashMap() {
super();
accessOrder = false;
}
調用hashmap的構造方法,然後設置了一個accessOrder 屬性值。
看下這個屬性值的解釋:
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
迭代順序
true:訪問順序
false:插入順序
再看父類構造器做了什麼:
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
一系列初始化操作,最後調了init方法,該方法在hashmap中實現爲空,linkedhashmap 做了實現。看下源碼
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
創建了一個雙向鏈表的頭結點。
3.2 看 hashmap 的put方法
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
如果已經存在相同key的元素,替換之,調用e.recordAccess(this) 方法。
如果table指定位置不存在元素或者有元素但是key不同,調用 addEntry(hash, key, value, i) 方法添加新元素.
接下來看下這兩個方法的實現
3.3 LinkedHashMap Entry.recordAccess 方法
hashmap中的實現爲空,linkedhashmap做了實現。
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) { //如果設置了按照訪問順序遍歷
lm.modCount++;
remove();//,刪除從鏈表中刪除當前節點
addBefore(lm.header);//將當前元素插入鏈表末尾
}
}
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry; //header
before = existingEntry.before;//header 的before 也就是尾節點
before.after = this;//當前尾節點的後一個節點指向當前節點
after.before = this;//頭結點的上一個節點指向當前節點
}
3.4 LinkedHashMap addEntry 方法
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);//創建元素,將元素添加到鏈表末尾
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {//判斷是否要移除最老的元素,實現LRU相關,默認爲false
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
createEntry 方法實現
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;//插入bucketIndex 位置entry鏈表的第一位
e.addBefore(header);//插入entry 雙向鏈表尾部
size++;
}
至此我們瞭解了LinkedHashMap 如何實現元素之間有序:添加元素的同時加入雙向鏈表
文章開頭講到LinkedHashMap有個 accessOrder 可以控制map遍歷時是按照訪問順序還是插入順序 遍歷,接下來先看下涉及元素訪問的方法get(put方法也算,上面分析過),然後看下LinkedHashMap 的迭代器。
3.5 LinkedHashMap get 方法
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);//調用次方法,前面分析過如果是設置按照訪問順序遍歷,將當前元素放入雙向列表的尾部,表示最新訪問
return e.value;
}
3.6 LinkedHashMap LinkedHashIterator
LinkedHashMap 定義了一個內部類LinkedHashIterator,直接遍歷雙向鏈表中的元素,從而實現按照訪問順序遍歷或者插入順序遍歷
private abstract class LinkedHashIterator<T> implements Iterator<T> {
Entry<K,V> nextEntry = header.after; //從雙向鏈表的頭節點開始遍歷
Entry<K,V> lastReturned = null;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
至此 LinkedHashMap 實現有序遍歷的原理分析完畢。接下來看看如何實現一個LRU 緩存
4.LRU緩存
上文提到過linkedhashmap 添加元素的方法 addEntry 中有個判斷 是否要刪除最老的元素,如果返回true 會刪除雙向鏈表中最老的元素,也就是頭節點後面的元素。重寫removeEldestEntry 即可實現LRU緩存.
例:
public class MyLRUMap<K,V> extends LinkedHashMap<K,V> {
private static final int MAX_SIZE = 4;
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_SIZE;
}
public static void main(String args[]) {
MyLRUMap<String,String> map = new MyLRUMap();
map.put("a","1");
map.put("c","2");
map.put("d","3");
map.put("e","4");
System.err.println(map);
map.put("f","5");
System.err.println(map);
}
}
結果
{a=1, c=2, d=3, e=4}
{c=2, d=3, e=4, f=5}