簡述
LinkedHashMap繼承了HashMap,其操作與HashMap類似,結構也差不多。與HashMap最大區別就是通過節點Entry增加了before和after屬性來維護順序使其有序。示例根據插入順序排序:
public static void main(String[] args) {
System.out.println("**********HashMap***********");
Map hashMap = new HashMap();
hashMap.put("Marvel", "漫威");
hashMap.put("Deadpool", "死侍");
hashMap.put("Hulk", "綠巨人");
hashMap.put("Thor", "雷神");
hashMap.put("Wolverine", "金剛狼");
for (Iterator it = hashMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry obj = (Map.Entry)it.next();
System.out.println(obj.getKey() + "-" +obj.getValue());
}
System.out.println("**********LinkedHashMap***********");
Map linkedHashMap = new LinkedHashMap();
linkedHashMap.put("Marvel", "漫威");
linkedHashMap.put("Deadpool", "死侍");
linkedHashMap.put("Hulk", "綠巨人");
linkedHashMap.put("Thor", "雷神");
linkedHashMap.put("Wolverine", "金剛狼");
for (Iterator it = linkedHashMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry obj = (Map.Entry)it.next();
System.out.println(obj.getKey() + "-" +obj.getValue());
}
}
複製程式碼
輸出:
**********HashMap***********
Thor-雷神
Deadpool-死侍
Wolverine-金剛狼
Marvel-漫威
Hulk-綠巨人
**********LinkedHashMap***********
Marvel-漫威
Deadpool-死侍
Hulk-綠巨人
Thor-雷神
Wolverine-金剛狼
複製程式碼
原始碼分析
LinkedHashMap欄位
final boolean accessOrder; //是否按照訪問順序,true:訪問順序,false:插入順序
transient LinkedHashMap.Entry head; //雙向連結串列頭節點
transient LinkedHashMap.Entry tail; //雙向連結串列尾結點
/**
* 節點類繼承了HashMap.Node,改成雙向連結串列
* next表示桶上連線的Entry順序
* before、after插入前後,插入順序(維護雙向連結串列)
*/
static class Entry extends HashMap.Node {
Entry before, after;
}
複製程式碼
構造方法
前四個預設插入排序,最後一個可指定排序,accessOrder為true時訪問順序,false時插入順序
public LinkedHashMap() {
super();
accessOrder = false;
}
//指定初始化容量
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//指定初始化容量和負載因子
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
//利用另一個map來構建
public LinkedHashMap(Map m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
//指定初始化容量、負載因子和是否按照訪問順序
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
複製程式碼
put方法
LinkedHashMap沿用了HashMap的put方法,不過重寫了其newNode()、afterNodeAccess()、afterNodeInsertion()方法
Node newNode(int hash, K key, V value, Node e) {
//呼叫LinkedHashMap的entry構造方法
LinkedHashMap.Entry p =
new LinkedHashMap.Entry(hash, key, value, e);
linkNodeLast(p);
return p;
}
/**
* 將新增節點置於連結串列尾部
*/
private void linkNodeLast(LinkedHashMap.Entry p) {
//獲取當前連結串列尾部節點
LinkedHashMap.Entry last = tail;
//將p設為尾部節點
tail = p;
//若當前集合為空,p既是頭節點又是尾節點
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
複製程式碼
從上面的原始碼可以看出,linkedHashMap額外維護了一個雙向連結串列。
再來看看afterNodeAccess()方法,在put方法若當前集合存在key物件進行替換value時會呼叫afterNodeAccess:
/**
* 將當前被訪問的節點移至雙向連結串列尾部
*/
void afterNodeAccess(Node e) { // move node to last
LinkedHashMap.Entry last;
//若accessOrder為true且原尾節點不是節點e
if (accessOrder && (last = tail) != e) {
//將節點e強轉為雙向連結串列節點p,獲取p插入前後的節點
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)e, b = p.before, a = p.after;
//因為需要將e置於連結串列尾部,所以將其after屬性設為null
p.after = null;
//對於雙向連結串列,若p的前驅節點為空,頭節點設為p的後繼
if (b == null)
head = a;
else
//否則將p前驅節點的後繼節點設為p的後繼節點
b.after = a;
//若p的後繼節點不為null,將p的後繼節點的前驅節點設為p的前驅節點
if (a != null)
a.before = b;
else
//否則將p的前驅節點設為尾結點
last = b;
//若原尾節點為空,將p設為頭節點
if (last == null)
head = p;
else {
//否則將p的前驅節點改為原尾節點,原尾節點的後繼節點改為p
p.before = last;
last.after = p;
}
//尾節點改為p
tail = p;
++modCount;
}
}
複製程式碼
在put方法中新增節點情況下最後會呼叫afterNodeInsertion()方法,原始碼如下:
/**
* 刪除雙向連結串列頭節點
*/
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//預設返回false
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
複製程式碼
void afterNodeInsertion(boolean evict)以及boolean removeEldestEntry(Map.Entry<K,V> eldest)是構建LruCache需要的回撥,在LinkedHashMap裡可以忽略它們
get方法
LinkedHashMap重寫了其get()方法
public V get(Object key) {
Node e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
複製程式碼
相對於HashMap的get操作,linkedHashMap多了一步操作,若accessOrder為true會呼叫afterNodeAccess()方法。afterNodeAccess()方法上面已經提及,需要注意的是在此方法會修改modCount即當迭代LinkedHashMap,若同時查詢訪問資料,會導致fail-fast,因為迭代順序變了
remove方法
LinkedHashMap沿用了HashMap的remove()方法,不過重寫了其afterNodeRemoval()方法
/**
* 將節點e從雙向連結串列中刪除
*/
void afterNodeRemoval(Node e) { // unlink
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)e, b = p.before, a = p.after;
//待刪除節點p的前驅後繼節點都置空
p.before = p.after = null;
//若前驅節點為空,則將p後繼節點設為頭節點
if (b == null)
head = a;
//否則將p後繼節點設為p前驅節點的後繼節點
else
b.after = a;
//若p後繼節點為空,則將p的前驅節點設為尾結點
if (a == null)
tail = b;
//否則p前驅節點設為p後繼節點的前驅節點
else
a.before = b;
}
複製程式碼
用LinkedHashMap實現緩衝機制
FIFO
FIFO(First In First Out):先入先出,和佇列一樣。舉個生活例子,超市購物收銀臺結賬先排隊的客戶先結賬離開
FIFO實現:LinkedHashMap預設(accessOrder為false)就是按儲存的資料排序,滿足先進先出,示例如下:
public class FIFOCache extends LinkedHashMap {
private final int maxSize;//限制資料
public FIFOCache(int maxSize) {
super();//呼叫父類預設構造方法,accessOrder為false
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
public static void main(String[] args) {
Map fifoCache = new FIFOCache(10);//限定10個
for (int i = 0; i < 10; i++) {
fifoCache.put(i, i);
}
System.out.println("初始情況:" + fifoCache.toString());
fifoCache.put(6, 6);//訪問已存在資料
System.out.println("已存在資料被訪問後:" + fifoCache.toString());
fifoCache.put(10, 10);
System.out.println("新增一個資料後:" + fifoCache.toString());
}
}
複製程式碼
輸出:
初始情況:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
已存在資料被訪問後:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
新增一個資料後:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10}
複製程式碼
從輸出結果中看,其滿足FIFO規則,按插入順序進行排序,若命中快取中的任意資料也不會破壞先進先出規則。若新增了一個快取外的資料會把最先插入的資料移除
LRU
public class LRUCache extends LinkedHashMap {
private final int maxSize;
public LRUCache(int maxSize){
super(maxSize, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
public static void main(String[] args) {
Map fifoCache = new LRUCache(10);//限定10個
for (int i = 0; i < 10; i++) {
fifoCache.put(i, i);
}
System.out.println("初始情況:" + fifoCache.toString());
fifoCache.put(6, 6);//訪問已存在資料
fifoCache.get(3);
System.out.println("已存在資料被訪問後:" + fifoCache.toString());
fifoCache.put(10, 10);
System.out.println("新增一個資料後:" + fifoCache.toString());
}
}
複製程式碼
輸出:
初始情況:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
已存在資料被訪問後:{0=0, 1=1, 2=2, 4=4, 5=5, 7=7, 8=8, 9=9, 6=6, 3=3}
新增一個資料後:{1=1, 2=2, 4=4, 5=5, 7=7, 8=8, 9=9, 6=6, 3=3, 10=10}
複製程式碼
從輸出結果中可以看出,符合LRU規則
總結
LinkedHashMap幾乎與HashMap一樣,其最大區別就是節點類多了before和after屬性額外維護雙向連結串列用來實現插入順序(預設)或訪問順序排序
參考
https://blog.csdn.net/u012403290/article/details/68926201