Java知识:LinkedHashMap详解

LinkedHashMap本质是什么?

与HashMap的异同:同样是基于散列表实现,区别是,LinkedHashMap内部多了一个双向循环链表的维护,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列,

简单地说:LinkedHashMap=散列表+循环双向链表

LinkeedHashMap是基于HashMap实现的,那它在哪里引入了双向链表呢?

首先,HashMap的构造方法都要调用一个方法 init(),而LinkedHashMap对这个方法进行了重写:

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
----->  init();
    }
@Override
    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

如果已经熟悉过HashMap,那么你一定可以发现,这里的Entry<>已经和HashMap中的Entry内部类不一样了,所以,LinkedHashMap重写了HashMap中最基本的单元Entry:

 /**
     * LinkedHashMap entry.
     */
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

        private void remove() {
            before.after = after;
            after.before = before;
        }

        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }

看到这段内部类的代码,我们可以总结出两个重要的特性:
(1)这里的 before 和 after 是增加的。此处的Entry它继承了HashMap.Entry,所以它的成员变量有:

hash值(这个容易被忽略)
key 和 value 两个变量
用于维护哈希表的 next
用于维护双向循环链表的 before 和 after
(2)LinkedHashMap中依然在使用 【桶+链表】 的典型哈希表结构。这一点从它复用的HashMap中的代码可以看出。所以,它依然会有resize,使用的是链表来维护顺序,所以,resize并不会破坏这个顺序的维护。

LinkedHashMap到底是按照插入顺序排列还是元素最近访问顺序(LRU)排列?

private final boolean accessOrder;
accessOrder:true则按照LRU算法迭代整个LinkedHashMap,false则按照元素的插入顺序迭代!

元素最近访问顺序(LRU)排列是什么样子的?

最后两个图是精髓,访问就是先删除后添加到尾部。

这里写图片描述

循环双向链表的头部存放的是最久访问的节点或最先插入的节点,尾部为最近访问的或最近插入的节点

如果上图太简单的话,下面有个例子还是不错的:

public static void main(String[] args) {
        LinkedHashMap<Integer, String> lMap = new LinkedHashMap<Integer, String>(2, 0.3f, true);

        for (int i = 0; i < 10; i++) {
            lMap.put(i, "@" + i);
        }
        System.out.println("**** 初始顺序:****");
        for (Map.Entry<Integer, String> entry : lMap.entrySet()) {
            String value = entry.getValue();
            System.out.printf("%-5s", value);
        }

        // 依次访问81473
        lMap.get(8);
        lMap.get(1);
        lMap.get(4);
        lMap.get(7);
        lMap.get(3);

        System.out.println("\n\n**** 访问过后的顺序:****");
        for (Map.Entry<Integer, String> entry : lMap.entrySet()) {
            String value = entry.getValue();
            System.out.printf("%-5s", value);
        }

        // put 新值
        lMap.put(11, "@" + 11);
        lMap.put(12, "@" + 12);
        lMap.put(13, "@" + 13);
        System.out.println("\n\n**** 插入新k-v后的顺序:****");
        for (Map.Entry<Integer, String> entry : lMap.entrySet()) {
            String value = entry.getValue();
            System.out.printf("%-5s", value);
        }

        // put 旧值
        lMap.put(0, "new" + 0);
        lMap.put(2, "new" + 2);
        lMap.put(4, "new" + 4);

        System.out.println("\n\n**** 插入旧k后的顺序:****");
        for (Map.Entry<Integer, String> entry : lMap.entrySet()) {
            String value = entry.getValue();
            System.out.printf("%-5s", value);
        }

    }

output:

**** 初始顺序:****
@0   @1   @2   @3   @4   @5   @6   @7   @8   @9   

**** 访问过后的顺序:****
@0   @2   @5   @6   @9   @8   @1   @4   @7   @3   

**** 插入新k-v后的顺序:****
@0   @2   @5   @6   @9   @8   @1   @4   @7   @3   @11  @12  @13  

**** 插入旧k后的顺序:****
@5   @6   @9   @8   @1   @7   @3   @11  @12  @13  new0 new2 new4 

实现的原理就是,每次get的时候,将这个元素移到队列的末尾,从而,最不常访问的元素在队列的首部。

参考资料:
http://www.cnblogs.com/chenpi/p/5294077.html
http://blog.csdn.net/cds86333774/article/details/50946990

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值