您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)Java中LinkedHashMap的作用是什么,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
put()
方法的講解。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
// ①、數(shù)組 table 為 null 時,調(diào)用 resize 方法創(chuàng)建默認(rèn)大小的數(shù)組
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// ②、計算下標(biāo),如果該位置上沒有值,則填充
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
}
這個公式 i = (n - 1) & hash
計算后的值并不是按照 0、1、2、3、4、5 這樣有序的下標(biāo)將鍵值對插入到數(shù)組當(dāng)中的,而是有一定的隨機(jī)性。
那 LinkedHashMap 就是為這個需求應(yīng)運而生的。LinkedHashMap 繼承了 HashMap,所以 HashMap 有的關(guān)于鍵值對的功能,它也有了。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>{}
此外,LinkedHashMap 內(nèi)部又追加了雙向鏈表,來維護(hù)元素的插入順序。注意下面代碼中的 before 和 after,它倆就是用來維護(hù)當(dāng)前元素的前一個元素和后一個元素的順序的。
static class Entry<K,V> extends HashMap.Node<K,V> {
LinkedHashMap.Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Node<K,V> next) {
super(hash, key, value, next);
}
}
關(guān)于雙向鏈表,同學(xué)們可以回頭看一遍我寫的 LinkedList 那篇文章,會對理解本篇的 LinkedHashMap 有很大的幫助。
在繼續(xù)下面的內(nèi)容之前,我先貼一張圖片,給大家增添一點樂趣——看我這心操的。UUID 那篇文章的標(biāo)題里用了“可笑”和“你”,結(jié)果就看到了下面這么樂呵的留言。
(到底是知道還是不知道,我搞不清楚了。。。)那 LinkedHashMap 這篇也用了“你”和“可笑”,不知道到時候會不會有人繼續(xù)對號入座啊,想想就覺得特別歡樂。
在 HashMap 那篇文章里,我有講解到一點,不知道同學(xué)們記不記得,就是 null 會插入到 HashMap 的第一位。
Map<String, String> hashMap = new HashMap<>();
hashMap.put("沉", "沉默王二");
hashMap.put("默", "沉默王二");
hashMap.put("王", "沉默王二");
hashMap.put("二", "沉默王二");
hashMap.put(null, null);
for (String key : hashMap.keySet()) {
System.out.println(key + " : " + hashMap.get(key));
}
輸出的結(jié)果是:
null : null
默 : 沉默王二
沉 : 沉默王二
王 : 沉默王二
二 : 沉默王二
雖然 null 最后一位 put 進(jìn)去的,但在遍歷輸出的時候,跑到了第一位。
那再來對比看一下 LinkedHashMap。
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("沉", "沉默王二");
linkedHashMap.put("默", "沉默王二");
linkedHashMap.put("王", "沉默王二");
linkedHashMap.put("二", "沉默王二");
linkedHashMap.put(null, null);
for (String key : linkedHashMap.keySet()) {
System.out.println(key + " : " + linkedHashMap.get(key));
}
輸出結(jié)果是:
沉 : 沉默王二
默 : 沉默王二
王 : 沉默王二
二 : 沉默王二
null : null
null 在最后一位插入,在最后一位輸出。
輸出結(jié)果可以再次證明,HashMap 是無序的,LinkedHashMap 是可以維持插入順序的。
那 LinkedHashMap 是如何做到這一點呢?我相信同學(xué)們和我一樣,非常希望知道原因。
要想搞清楚,就需要深入研究一下 LinkedHashMap 的源碼。LinkedHashMap 并未重寫 HashMap 的 put()
方法,而是重寫了 put()
方法需要調(diào)用的內(nèi)部方法 newNode()
。
HashMap.Node<K,V> newNode(int hash, K key, V value, HashMap.Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
linkNodeLast(p);
return p;
}
前面說了,LinkedHashMap.Entry 繼承了 HashMap.Node,并且追加了兩個字段 before 和 after。
那,緊接著來看看 linkNodeLast()
方法:
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
看到了吧,LinkedHashMap 在添加第一個元素的時候,會把 head 賦值為第一個元素,等到第二個元素添加進(jìn)來的時候,會把第二個元素的 before 賦值為第一個元素,第一個元素的 afer 賦值為第二個元素。
這就保證了鍵值對是按照插入順序排列的,明白了吧?
注:我用到的 JDK 版本為 14。
LinkedHashMap 不僅能夠維持插入順序,還能夠維持訪問順序。訪問包括調(diào)用 get()
方法、remove()
方法和 put()
方法。
要維護(hù)訪問順序,需要我們在聲明 LinkedHashMap 的時候指定三個參數(shù)。
LinkedHashMap<String, String> map = new LinkedHashMap<>(16, .75f, true);
第一個參數(shù)和第二個參數(shù),看過 HashMap 的同學(xué)們應(yīng)該很熟悉了,指的是初始容量和負(fù)載因子。
第三個參數(shù)如果為 true 的話,就表示 LinkedHashMap 要維護(hù)訪問順序;否則,維護(hù)插入順序。默認(rèn)是 false。
Map<String, String> linkedHashMap = new LinkedHashMap<>(16, .75f, true);
linkedHashMap.put("沉", "沉默王二");
linkedHashMap.put("默", "沉默王二");
linkedHashMap.put("王", "沉默王二");
linkedHashMap.put("二", "沉默王二");
System.out.println(linkedHashMap);
linkedHashMap.get("默");
System.out.println(linkedHashMap);
linkedHashMap.get("王");
System.out.println(linkedHashMap);
輸出的結(jié)果如下所示:
{沉=沉默王二, 默=沉默王二, 王=沉默王二, 二=沉默王二}
{沉=沉默王二, 王=沉默王二, 二=沉默王二, 默=沉默王二}
{沉=沉默王二, 二=沉默王二, 默=沉默王二, 王=沉默王二}
當(dāng)我們使用 get()
方法訪問鍵位“默”的元素后,輸出結(jié)果中,默=沉默王二
在最后;當(dāng)我們訪問鍵位“王”的元素后,輸出結(jié)果中,王=沉默王二
在最后,默=沉默王二
在倒數(shù)第二位。
也就是說,最不經(jīng)常訪問的放在頭部,這就有意思了。有意思在哪呢?
我們可以使用 LinkedHashMap 來實現(xiàn) LRU 緩存,LRU 是 Least Recently Used 的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。
public class MyLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 5;
public MyLinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
}
MyLinkedHashMap 是一個自定義類,它繼承了 LinkedHashMap,并且重寫了 removeEldestEntry()
方法——使 Map 最多可容納 5 個元素,超出后就淘汰。
我們來測試一下。
MyLinkedHashMap<String,String> map = new MyLinkedHashMap<>(16,0.75f,true);
map.put("沉", "沉默王二");
map.put("默", "沉默王二");
map.put("王", "沉默王二");
map.put("二", "沉默王二");
map.put("一枚有趣的程序員", "一枚有趣的程序員");
System.out.println(map);
map.put("一枚有顏值的程序員", "一枚有顏值的程序員");
System.out.println(map);
map.put("一枚有才華的程序員","一枚有才華的程序員");
System.out.println(map);
輸出結(jié)果如下所示:
{沉=沉默王二, 默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員}
{默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員, 一枚有顏值的程序員=一枚有顏值的程序員}
{王=沉默王二, 二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員, 一枚有顏值的程序員=一枚有顏值的程序員, 一枚有才華的程序員=一枚有才華的程序員}
沉=沉默王二
和 默=沉默王二
依次被淘汰出局。
假如在 put “一枚有才華的程序員”之前 get 了鍵位為“默”的元素:
MyLinkedHashMap<String,String> map = new MyLinkedHashMap<>(16,0.75f,true);
map.put("沉", "沉默王二");
map.put("默", "沉默王二");
map.put("王", "沉默王二");
map.put("二", "沉默王二");
map.put("一枚有趣的程序員", "一枚有趣的程序員");
System.out.println(map);
map.put("一枚有顏值的程序員", "一枚有顏值的程序員");
System.out.println(map);
map.get("默");
map.put("一枚有才華的程序員","一枚有才華的程序員");
System.out.println(map);
那輸出結(jié)果就變了,對吧?
{沉=沉默王二, 默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員}
{默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員, 一枚有顏值的程序員=一枚有顏值的程序員}
{二=沉默王二, 一枚有趣的程序員=一枚有趣的程序員, 一枚有顏值的程序員=一枚有顏值的程序員, 默=沉默王二, 一枚有才華的程序員=一枚有才華的程序員}
沉=沉默王二
和 王=沉默王二
被淘汰出局了。
那 LinkedHashMap 是如何來維持訪問順序呢?同學(xué)們感興趣的話,可以研究一下下面這三個方法。
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
afterNodeAccess()
會在調(diào)用 get()
方法的時候被調(diào)用,afterNodeInsertion()
會在調(diào)用 put()
方法的時候被調(diào)用,afterNodeRemoval()
會在調(diào)用 remove()
方法的時候被調(diào)用。
我來以 afterNodeAccess()
為例來講解一下。
void afterNodeAccess(HashMap.Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
哪個元素被 get 就把哪個元素放在最后。了解了吧?
那同學(xué)們可能還想知道,為什么 LinkedHashMap 能實現(xiàn) LRU 緩存,把最不經(jīng)常訪問的那個元素淘汰?
在插入元素的時候,需要調(diào)用 put()
方法,該方法最后會調(diào)用 afterNodeInsertion()
方法,這個方法被 LinkedHashMap 重寫了。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
removeEldestEntry()
方法會判斷第一個元素是否超出了可容納的最大范圍,如果超出,那就會調(diào)用 removeNode()
方法對最不經(jīng)常訪問的那個元素進(jìn)行刪除。
上述就是小編為大家分享的Java中LinkedHashMap的作用是什么了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。