java-map之LinkedHashMap
1.1概述
在使用HashMap的時候,可能會遇到需要按照當時put的順序來進行雜湊表的遍歷。通過上篇對HashMap的瞭解,我們知道HashMap中不存在儲存順序的機制。本篇文章要介紹的LinkedHashMap專為此特性而生。在LinkedHashMap中可以保持兩種順序,分別是插入順序和訪問順序,這個是可以在LinkedHashMap的初始化方法中進行指定的。相對於訪問順序,按照插入順序進行編排被使用到的場景更多一些,所以預設是按照插入順序進行編排。
1.2結構
所以該結構其實就是用雙向連結串列加hashmap實現的,
1.3特點
- key和value都允許為空
- key重複會覆蓋,value可以重複
- 有序的
- LinkedHashMap是非執行緒安全的
1.4詳解
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
可以看到,LinkedHashMap繼承了HashMap,實現了Map介面。
在屬性上它比HashMap多了兩個屬性:
//連結串列的頭結點 private transient Entry<K,V> header; //該屬性指取得鍵值對的方式,是個布林值,false表示插入順序,true表示訪問順序,也就是訪問次數. private final boolean accessOrder;
LinkedHashMap有五個構造器:
//用預設的初始容量和負載因子構建一個LinkedHashMap,取出鍵值對的方式是插入順序 public LinkedHashMap() { super(); accessOrder = false; } //構造一個指定初始容量的LinkedHashMap,取得鍵值對的順序 public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } //構造一個指定初始容量和負載因子,按照插入順序的LinkedHashMap public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } //根據給定的初始容量,負載因子和鍵值對迭代順序構建一個LinkedHashMap public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } //通過給定的map建立一個LinkedHashMap,負載因子是預設值,迭代方式是插入順序. public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = false; }
其實主要區別還是在基本資料結構:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
LinkedHashMap的Entry類繼承了HashMap的Entry,並在此基礎上進行了擴充套件,它擁有以下屬性:
K key;
V value;
Entry<K, V> next;
int hash;
Entry<K, V> before;
NEtry<K, V> after;
LinkedHashMap的初始化實際上是先呼叫HashMap的初始化,最後呼叫LinkedHashMap的init()使得header初始化。
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
在header中,hash值為-1,其他都為null,也就是說這個header不在陣列table中,其實它就是用來指示開源元素、標記結束元素的.header的目的就是為了記錄第一個插入的元素是誰,在遍歷的時候能夠找到第一個元素
LinkedHashMap儲存元素只是重寫了寫了父類put方法邏輯中呼叫的子方法addEntry()和createEntry()。
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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++;
//在這裡呼叫的是LinkedHashMap重寫後的addEntry方法,這之前的和HashMap一樣
addEntry(hash, key, value, i);
return null;
}
在最後一步呼叫LinkedHashMap的addEntry方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
//呼叫HashMap的addEntry,在map部分新增元素。
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//在這裡呼叫LinkedHashMap自己的createEntry方法.
createEntry(hash, key, value, bucketIndex);
}
LinkedHashMap自己的createEntry()方法:
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
其實以上的操作和HashMap的操作沒有什麼不同,都是把新新增的節點放在了table[bucketIndex]位置上,差別在於LinkedHashMap還做了addBefore操作,而addBefore方法的目的就是讓新的Entry和原連結串列生成一個雙向連結串列.
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
LinkedHashMap獲得元素:
public V get(Object key) {
//呼叫父類的getEntry方法獲取元素
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
//該方法用來記錄訪問順序.
e.recordAccess(this);
return e.value;
}
可以看到,在get方法中,它是呼叫了父類的getEntry方法來獲取到元素,之後再呼叫自己的recordAccess()方法:
void recordAccess(HashMap<K,V> m) {
//因為在之前是轉型為父類物件來獲取entry的,所以這裡要轉回LinkedHashMap,判斷獲取資料的方式.
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//當LinkedHashMap按照訪問來排序時
if (lm.accessOrder) {
lm.modCount++;
//移除當前節點
remove();
//將當前節點插入到頭節點前面,即最後面.
addBefore(lm.header);
}
}
1.5應用
實現LRU演算法:
public class LRUCache extends LinkedHashMap
{
public LRUCache(int maxSize)
{
super(maxSize, 0.75F, true);
maxElements = maxSize;
}
protected boolean removeEldestEntry(java.util.Map.Entry eldest)
{
return size() > maxElements;
}
private static final long serialVersionUID = 1L;
protected int maxElements;
}
然後重寫一下recordAccess()方法:
``java
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
```java
private void remove() {
before.after = after;
after.before = before;
}
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
當然,也可以自定義實現該演算法,和上面這種繼承方式不一樣的的地方在於LRUCache類的節點中包含的是三個成員變數,一個最大容量,一個雙向連結串列,一個map集合。然後在LRUCache的增刪改查中封裝雙向連結串列和map集合的api函式。