Java集合(十)--LinkedHashMap简析

漏掉一个Map,补一下

LinkedHashMap是HashMap的子类,此实现与HashMap的不同之处在于它维护了一个贯穿其所有条目的双向链表。 此链表定义迭代排序,通常是键插入映射的顺序(插入顺序)。 请注意,如果将键重新插入Map,则不会影响插入顺序。

定义及说明

定义如下:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}

它继承于HashMap,底层使用哈希表和链表来维护集合。

构造方法为:

//此链表哈希映射的迭代排序方法:true表示访问顺序,false表示插入顺序。
final boolean accessOrder;

//使用指定的初始容量和加载因子构造一个空的插入排序的LinkedHashMap实例
public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

//使用指定的初始容量和默认加载因子(0.75)构造一个空的插入排序的LinkedHashMap实例
public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

//使用默认初始容量(16)和加载因子(0.75)构造一个空的插入顺序LinkedHashMap实例。
public LinkedHashMap() {
        super();
        accessOrder = false;
    }

//构造一个插入有序的LinkedHashMap实例,其具有与指定映射相同的映射。 LinkedHashMap实例使用默认加载因子(0.75)和足以保存指定映射中的映射的初始容量创建。
public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

//使用指定的初始容量,加载因子和排序模式构造一个空的LinkedHashMap实例。
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

我们可以看到,LinkedHashMap是调用父类的构造函数做初始化。其构造函数中的accessOrder属性决定了迭代顺序,true表示访问顺序,false表示插入顺序。默认使用插入顺序,即使用通过put或其类似方法插入的顺序进行排序。

源码及简析

按照惯例应该是分析put()方法了,但是,很尴尬,LinkedHashMap并没有重写put()方法,也就是说,它的put()方法跟HashMap是相同的。但是,他重写了putVal()方法中调用的afterNodeAccess()和afterNodeInsertion()方法,我们看一下:

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

//双向链表的头部
transient LinkedHashMapEntry<K,V> head;

//双向链表的尾部
transient LinkedHashMapEntry<K,V> tail;


//将节点移动到最后
void afterNodeAccess(Node<K,V> e) {
        LinkedHashMapEntry<K,V> last;
        //如果是按访问顺序排序,且e不是链表的尾部
        if (accessOrder && (last = tail) != e) {
            //将e节点赋值给p,将e的前节点赋值给b,将e的后节点赋值给a,将tail赋值给last
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            //将p的后一个节点置为空
            p.after = null;
            //如果p为链表头部
            if (b == null)
                //将a置为链表头部
                head = a;
            else
                //否则,将a置为b的后一个元素
                b.after = a;
            //如果p不是链表的尾部
            if (a != null)
                //将a的上一个元素置为b
                a.before = b;
            else
                //否则,将b赋值给last
                last = b;
            //当前链表如果为空
            if (last == null)
                //将p置为链表的头部
                head = p;
            else {
                //否则,将p置为last的下一个元素
                p.before = last;
                last.after = p;
            }
            //将p置为链表的尾部
            tail = p;
            ++modCount;
        }
    }

//在HashMap中传过来的evict为true
void afterNodeInsertion(boolean evict) {
        LinkedHashMapEntry<K,V> first;
        //判断是否删除过时的条目
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            //调用HashMap的方法
            removeNode(hash(key), key, null, false, true);
        }
    }

//如果此映射应删除其最旧条目,则返回true。它允许映射通过删除过时条目来减少内存消耗。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        //糟老头子坏的很,它直接返回了false!!!
        return false;
    }

通过上面分析,我们发现,如果是按照访问顺序排序,则经过一系列的判断和赋值后,将新元素插入到链表的尾部。也就是说,如果按照访问的顺序排序,则排序靠后的,就是最近插入的元素。要注意,这个排序跟在Map中的存储顺序没有关系,只是在双向链表中的顺序。我们还发现在LinkedHashMapEntry类内部,出现了before和after两个属性,而根据在afterNodeAccess()方法中的分析,他们分别指向元素的上一个和下一个节点。那么,由此得出,在LinkedHashMap中的链表是一个双向链表。

删除条目时,调用了HashMap的remove()方法,LinkedHashMap重写了其中调用的afterNodeRemoval(node)方法(在removeNode方法中调用),我们现在来看一下:

void afterNodeRemoval(Node<K,V> e) {
        //将e的值赋予p,将p的上个节点的值赋予b,p的下个节点的值赋予a
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        //将p前、后节点置为空
        p.before = p.after = null;
        //如果p为链表头部
        if (b == null)
            //将a设置为链表头部
            head = a;
        else
            //否则,将b的下个元素由变为a
            b.after = a;
        //如果p为链表尾部
        if (a == null)
            //将b设为链表尾部
            tail = b;
        else
            //否则,将b设置为a的上个元素
            a.before = b;
    }

可以看到,通过对指定元素e前后节点b、a的设置,使得其b、a的指向不再指向p,以将其删除。

接下来,我们看一下它的get()方法:

public V get(Object key) {
        //初始化一个节点对象
        Node<K,V> e;
        //判断key所对应的节点是否为空,不为空则获取key所对应的节点值
        if ((e = getNode(hash(key), key)) == null)
            return null;
        //判断排序方式
        if (accessOrder)
            //如果是访问顺序,则重排序
            afterNodeAccess(e);
        return e.value;
    }


//重排序(或是调用了put()方法之后,为节点设置前、后位置的元素)
void afterNodeAccess(Node<K,V> e) { 
        LinkedHashMapEntry<K,V> last;
        //判断排序方式是按访问顺序排序,且e节点不是双向链表的尾部
        if (accessOrder && (last = tail) != e) {
            //将e节点赋值给p,将e的前节点赋值给b,将e的后节点赋值给a
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            //将p的后一个节点置为空
            p.after = null;
            if (b == null)
                //如果b为空,证明e为首节点,则将e的下个节点置为首节点
                head = a;
            else
                //如果b不为空,则将a置为其下个节点,替代e的位置
                b.after = a;
            if (a != null)
                //如果a不为空,则将a的上一个节点置为b,替代e的位置
                a.before = b;
            else
                //如果a为空,则将b的值赋值给last
                last = b;
            if (last == null)
                //如果last为空(即tail为空,且b为空,说明只有p一个元素),则将p置为首节点
                head = p;
            else {
                //如果last不为空,则将p的前节点置为last,last的后节点置为p
                p.before = last;
                last.after = p;
            }
            /将p置为尾节点
            tail = p;
            ++modCount;
        }
    }

可以发现,我们把get方法访问过的元素放到链表的尾部,而通过上面的分析,我们发现,如果主动删除元素,则从链表的头部开始。这样,就形成了一个简单的LRU。不过,由于其removeEldestEntry()方法直接返回了false,所以,如果我们要用它做LRU,就需要自己写一个类继承LinkedHashMap,并重写removeEldestEntry()方法。比如,我们要求Map的长度在100以内:

private static final int MAX_ENTRIES = 100;

@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
     return size() > MAX_ENTRIES;
}

小结

1、LinkedHashMap继承于HashMap,是一个基于HashMap和双向链表实现的Map。

2、LinedHashMap是有顺序的,分别为按照插入顺序排序和按照访问顺序排序,默认为按照插入顺序排序。

3、LinkedHashMap是非同步的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Collection & Map Collection 子类有 List 和 Set List --> Array...
    任教主来也阅读 3,140评论 1 9
  • 1 前言 LinkedHashMap继承于HashMap,如果对HashMap原理还不清楚的同学,请先看上一篇:图...
    唐江旭阅读 196,335评论 37 325
  • Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构如栈,队列等,Java集合还可以用于保存具有映射关...
    小徐andorid阅读 1,900评论 0 13
  • 郁孤台下清江水,中间多少行人泪。西北望长安,可怜无数山,青山遮不住,毕竟东流去。
    加百列v阅读 52评论 0 0
  • ———分享趣味诗 上班族往下看 秋 似酒 味醇厚 ...
    淡似闲云阅读 417评论 3 15