LinkedHashMap繼承了HashMap,其操做與HashMap相似,結構也差很少。與HashMap最大區別就是經過節點Entry增長了before和after屬性來維護順序使其有序。示例根據插入順序排序:node
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-金剛狼
複製代碼
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時插入順序ide
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;
}
複製代碼
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:this
/**
* 將當前被訪問的節點移至雙向鏈表尾部
*/
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()方法,源碼以下:spa
/**
* 刪除雙向鏈表頭節點
*/
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裏能夠忽略它們.net
LinkedHashMap重寫了其get()方法code
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,由於迭代順序變了cdn
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;
}
複製代碼
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規則,按插入順序進行排序,若命中緩存中的任意數據也不會破壞先進先出規則。若新增了一個緩存外的數據會把最早插入的數據移除
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