LinkedHashMap源碼解析

一 成員變量解析

/**
 * 雙向鏈表頭節(jié)點
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * 雙向鏈表尾節(jié)點
 */
transient LinkedHashMap.Entry<K,V> tail;

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

/**
 * true按訪問排序 false按插入排序
 */
final boolean accessOrder;

二 關(guān)鍵方法解析

1 添加元素

//該方法繼承于HashMap,LinkedHashMap只重寫了newNode()和afterNodeAccess()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        // LinkedHashMap重寫newNode()方法
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // LinkedHashMap重寫該方法
            // 如果key對應(yīng)的entry存在矮嫉,按訪問更新鏈表順序
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// 將節(jié)點插入鏈表尾部趋距,保證插入的順序不變
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // 如果尾部為null 則將p設(shè)為頭部
    if (last == null)
        head = p;
    else {
        // 如果last不為null 則將p放到last后面粒氧,至此p為尾部節(jié)點
        p.before = last;
        last.after = p;
    }
}

// LinkedHashMap將此函數(shù)重寫,用于當(dāng)訪問某一節(jié)點后节腐,將該節(jié)點放到尾部
// 所以最近最少訪問的節(jié)點在頭部外盯,最頻繁訪問的節(jié)點在尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // 若accessOrder為true且節(jié)點e不是尾節(jié)點,則設(shè)置e為tail節(jié)點 
    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;
    }
}

2 獲取元素

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 若accessOrder為true翼雀,則將訪問的節(jié)點放入鏈表尾部
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

LinkedHashMap 繼承了 HashMap饱苟,是 HashMap 的子類,所以其他方法和 HashMap 基本相同狼渊,在此不必贅述

三 LinkedHashMap可以實現(xiàn)LRU緩存調(diào)度算法

前面講了LinkedHashMap添加元素箱熬,刪除类垦、修改元素就不說了,比較簡單城须,和HashMap+LinkedList的刪除蚤认、修改元素大同小異,下面講一個新的內(nèi)容糕伐。
LinkedHashMap可以用來作緩存砰琢,比方說LRUCache,看一下這個類的代碼良瞧,很簡單陪汽,就十幾行而已:

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;
}

LinkedHashMap 可以實現(xiàn)LRU算法的緩存基于兩點:

  1. LinkedList 首先它是一個 Map,Map是基于K-V的褥蚯,和緩存一致
  2. LinkedList 提供了一個 boolean 值可以讓用戶指定是否實現(xiàn)LRU
    那么挚冤,首先我們了解一下什么是LRU:LRU即Least Recently Used,最近最少使用赞庶,也就是說训挡,當(dāng)緩存滿了,會優(yōu)先淘汰那些最近最不常訪問的數(shù)據(jù)歧强。比方說數(shù)據(jù)a舍哄,1天前訪問了;數(shù)據(jù)b誊锭,2天前訪問了,緩存滿了弥锄,優(yōu)先會淘汰數(shù)據(jù)b丧靡。
    我們看一下LinkedList帶boolean型參數(shù)的構(gòu)造方法:
public LinkedHashMap(int initialCapacity,
         float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

就是這個accessOrder,它表示:
(1)false籽暇,所有的Entry按照插入的順序排列
(2)true温治,所有的Entry按照訪問的順序排列
第二點的意思就是,如果有1戒悠,2熬荆,3這3個Entry,那么訪問了1绸狐,就把1移到尾部去卤恳,即2,3寒矿,1突琳。每次訪問都把訪問的那個數(shù)據(jù)移到雙向隊列的尾部去,那么每次要淘汰數(shù)據(jù)的時候符相,雙向隊列最頭的那個數(shù)據(jù)不就是最不常訪問的那個數(shù)據(jù)了嗎拆融?換句話說,雙向鏈表最頭的那個數(shù)據(jù)就是要淘汰的數(shù)據(jù)。
“訪問”镜豹,這個詞有兩層意思:
(1)根據(jù)Key拿到Value傲须,也就是get方法
(2)修改Key對應(yīng)的Value,也就是put方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趟脂,一起剝皮案震驚了整個濱河市泰讽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌散怖,老刑警劉巖菇绵,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镇眷,居然都是意外死亡咬最,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門欠动,熙熙樓的掌柜王于貴愁眉苦臉地迎上來永乌,“玉大人,你說我怎么就攤上這事具伍〕岢” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵人芽,是天一觀的道長望几。 經(jīng)常有香客問我,道長萤厅,這世上最難降的妖魔是什么橄抹? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮惕味,結(jié)果婚禮上楼誓,老公的妹妹穿的比我還像新娘。我一直安慰自己名挥,他們只是感情好疟羹,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禀倔,像睡著了一般榄融。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上救湖,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天剃袍,我揣著相機與錄音,去河邊找鬼捎谨。 笑死民效,一個胖子當(dāng)著我的面吹牛憔维,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畏邢,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼业扒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舒萎?” 一聲冷哼從身側(cè)響起程储,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎臂寝,沒想到半個月后章鲤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡咆贬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年败徊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掏缎。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡皱蹦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出眷蜈,到底是詐尸還是另有隱情沪哺,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布酌儒,位于F島的核電站辜妓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忌怎。R本人自食惡果不足惜嫌拣,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呆躲。 院中可真熱鬧,春花似錦捶索、人聲如沸插掂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辅甥。三九已至,卻和暖如春燎竖,著一層夾襖步出監(jiān)牢的瞬間璃弄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工构回, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夏块,地道東北人疏咐。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像脐供,于是被迫代替她去往敵國和親浑塞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容