Java基础(七)-LinkedHashMap原理分析

一、前言

我们都知道,HashMap是基于散列表,插入是无序的,但是通过key-value的键值对能非常方便的存取数据。如果我们想既保留键值对的高效数据存取,又能保证键值对按插入顺序或访问顺序排列,如何实现?
就是本篇文章需要分析的:LinkedHashMap。

LinkedHashMap继承自HashMap,保留了其如下特点:

  • Key和Value都允许空;
  • Key重复会覆盖、Value允许重复;
  • 非线程安全。
二、LinkedHashMap解析

两个成员变量

private transient LinkedHashMapEntry<K,V> header; //双向链表头结点,在init方法里被实例化

private final boolean accessOrder; //控制链表排序方式 ,fasle为按插入顺序,true为按访问顺序,默认为false。

构造方法

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

有好几个重载的构造方法,只有如上方法可以动态设置accessOrder,其余的accessOrder都为false.
另外还有两个参数顺便了解下:
capacity: HashMap桶的数量,默认16,之后按2的幂扩展.
loadfactor: 装载因子,来衡量HashMap满的程度,默认0.75f.

put方法

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        // 计算hash值
        int hash = hash(key);
        // 得到在table数组中的index
        int i = indexFor(hash, table.length);
        // 遍历table[index],判断是否key已经存在,存在则替换,并返回旧值
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        // 如果key之前在table中不存在,则调用addEntry,LinkedHashMap重写了该方法
        addEntry(hash, key, value, i);
        return null;
    }

LinkedHashMap并没有重写HashMap的put方法,但是重写了put里的recordAccess和addEntry方法。

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) { //fasle为按插入顺序,true为按访问顺序
                lm.modCount++;
                remove(); // 把更新的Entry从双向链表中移除
                addBefore(lm.header);// 最近访问的元素被放到了链表头
            }
        }
private void remove() {
    before.after = after;
    after.before = before;
}
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}

recordAccess:主要针对按访问顺序排序,把当前相同实体从链表中删除,最近访问的元素被加到链表头。
recordAccess为true是LRU,即按访问顺序,就是说你访问了一个key,这个key就跑到了最后面,而为false是按插入顺序,默认为false。

  void addEntry(int hash, K key, V value, int bucketIndex) {
        // 调用父类的addEntry,增加一个Entry到HashMap中
        super.addEntry(hash, key, value, bucketIndex);
...
    }

  void addEntry(int hash, K key, V value, int bucketIndex) {
      ...
        // LinkedHashMap进行了重写
        createEntry(hash, key, value, bucketIndex);
    }

void createEntry(int hash, K key, V value, int bucketIndex) {
       HashMap.Entry<K,V> old = table[bucketIndex];
       // e就是新创建了Entry,会加入到table[bucketIndex]的表头
       Entry<K,V> e = new Entry<>(hash, key, value, old);
       table[bucketIndex] = e;
       // 把新创建的Entry,加入到双向链表表头
       e.addBefore(header);
       size++;
   }

addEntry:新的实体,直接创建并加入表头。

get方法

public V get(Object key) {
        // 调用genEntry得到Entry
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        // 如果LinkedHashMap是访问顺序的,则get时,也需要重新排序
        e.recordAccess(this);
        return e.value;
    }

LinkedHashMap有对get方法进行了重写,且同样调用recordAccess,重新排列下自己的双向链表。

总结加入元素过程:

遍历:

LinkedHashMap lhm = new LinkedHashMap<>(16, 0.75f, true);
  lhm.put("name1", "stan");
  lhm.put("name2", "james");
  lhm.put("name3", "mario");
  //lhm.put("name1", "stan");
  Set entrySet = lhm.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
   Entry entry = (Entry) iterator.next();
   String key = (String) entry.getKey();
   String value = (String) entry.getValue();
   System.out.println("key:" + key + ",value:" + value);
}

如上代码输入结果:

key:name1,value:stan
key:name2,value:james
key:name3,value:mario

如果打开注释项,则结果变为:

key:name2,value:james
key:name3,value:mario
key:name1,value:stan

这个重写插入过程:

先断开Entry1, head before指针指向它,after指向指向Entry2,Entry1 的after指针指向Entry3.这个过程相当于把Entry1从队尾移动到了队头。

那为何是最后打印呢?再看遍历源码:

    private abstract class LinkedHashIterator<T> implements Iterator<T> {
        // 默认下一个返回的Entry为双向链表表头的下一个元素
        Entry<K,V> nextEntry    = header.after;
        Entry<K,V> lastReturned = null;
        public boolean hasNext() {
            return nextEntry != header;
        }
        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();
            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
        }
        ...
    }

LinkedHashMap的迭代器,很明显看到,他是从head的after指针开始遍历的。新加入的元素放在队头,但是最终从队尾变量,所以既保证了插入顺序,又保证了访问属性,非常灵活,仅仅通过一个双向链表实现。而且并没有破坏HashMap的结构。所以说LinkedHashMap = HashMap + 循环双向链表。

最后一张图对比下HashMap与LinkedHashMap数据结构的区别:

图片使用:
https://www.jianshu.com/p/8f4f58b4b8ab

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

推荐阅读更多精彩内容