Java中LinkedHashMap底层实现

目录

序言:

1:LinkedHashMap中的put(key,value)

1.1:LinkedHashMap中对putVal()方法源码描述:

1.2:LinkedHashMap中对节点操作方法源码描述:

1.3:LinkedHashMap中afterNodeAcces()方法源码描述:

1.4:LinkedHashMap中afterNodeInsertion()方法源码描述:

1.5:LinkedHashMap中afterNodeRemoval()方法描述:

2:LinkedHashMap中get(key)源码描述:

3:LinkedHashMap中迭代源码描述

4:总结:

5:Demo代码

6:应用


序言:

LinkedHashMap从类名中可以看出此类与链表于HashMap,如下图所示

基于JDK1.8

 

从上述源码可以看出LinkedHashMap继承于HashMap,也就是说HashMap拥有的能力LinkedHashMap也同时拥有,与HashMap不同在于LinkedHashMap中维护了一个双向链表(此链表会中包含了所有数据),并且重写Node对象,并通过记录双向链表的始终(head,tail)节点用于保证此类可以做到顺序读写(并不仅仅如此)。

1:LinkedHashMap中的put(key,value)

之前对HashMap描述的一样,我们从put(key,value),到get(key),开始一步步查看数据在LinkedHashMap中的运行过程。查看源码后发现在LinkedHashMap中并没有重写put(key,value)方法,还是使用了HashMap中put方法,所以对于数据写入的整体流程和HashMap没什么大的区别,不同在于LinkedHashMap对所有对于数据节点的操作(newNode,replacementNode,newTreeNode,replacementTreeNode)都进行了重写。我们知道在HashMap的数据结构,所有数据会根据key将数据分布在不同位桶中,每个位桶以链表(若超过8为改变为红黑树)的结构存在,所以在HashMap从数据层面上来看局部是联系一起,整体却是离散的。LinkedHashMap在数据存储的结构大体与HashMap相同,不同在于重写的这些节点方法会将所有数据紧密的联系在一起(每个节点都会按照写入顺序记录上一个节点),所以LinkedHashMap在数据层面上来看局部是离散的,整体上是联系在一起的。整体的写入过程可以如下图所示:

HashMap中数据存储结构

 

LinkedHashMap中数据存储结构

1.1:LinkedHashMap中对putVal()方法源码描述:

 

对照着HashMap中putVal()讲解:

putVal()源码

 

1.2:LinkedHashMap中对节点操作方法源码描述:

    /**
     *重写HashMap中newNode方法 
     */
    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;
    }
    /**
     * 将当前节点追加到双向链表 写入过程的时间复杂度为O(1)
     */
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        //判断双向链表中尾是否为null
        if (last == null)
            //说明此链表为null 将当前节点置为链表头
            head = p;
        else {
            //说明已存在链表 将当前的节点加入链表中
            p.before = last;
            last.after = p;
        }
    }
    /**
     *该方法的作用将双向链表中某节点替换为其它节点 替换的时间复杂度为O(1)
     */
    private void transferLinks(LinkedHashMap.Entry<K,V> src,
                               LinkedHashMap.Entry<K,V> dst) {
        LinkedHashMap.Entry<K,V> b = dst.before = src.before;
        LinkedHashMap.Entry<K,V> a = dst.after = src.after;
        if (b == null)
            head = dst;
        else
            b.after = dst;
        if (a == null)
            tail = dst;
        else
            a.before = dst;
    }
    /**
     * 替换节点
     */
    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        LinkedHashMap.Entry<K,V> t =
            new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }
    /**
     * 创建一个红黑树节点(当位桶中元素超过8个转变为红黑树之后)
     */
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        //将创建树节点加入双向链表中
        linkNodeLast(p);
        return p;
    }
    /**
     * 替换树节点
     */
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }
 

1.3:LinkedHashMap中afterNodeAcces()方法源码描述:

    //此方法作用每次访问某节点被访问之后将当前节点移动到双向链中最后一位  
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        /**
         * accessOrder作用:查询迭代时的访问顺序
         * 值为true时,表示按照访问顺序迭代;如果key被操作过,那么此key对应的节点就会被移动到双向链表的head端基于此特性可以作为LRU算法的Java实现 后续详细介绍
         * 值为false时,表示按照插入顺序迭代
         * 该参数LinkedHashMap初始化的时候指定 不指定默认为false
         *
         */
        if (accessOrder && (last = tail) != e) {
            
            LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
           /**
           *以下的各种交换可以如下图所示
           * 就是将当前节点e从双向链中移动到最后一个位置,如果当前节点已经是最后了不做任何处理
           */
            //将当前节点下一个节点置null
            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;
        }
    }

 

1.4:LinkedHashMap中afterNodeInsertion()方法源码描述:

    //当有新的节点插入之后需要进行的动作
    //参数evict 是否开启驱逐操作,即当一个新的元素插入之后是否需要移除一个元素
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first; 
        //需要满足开启驱逐策略并且当前链表中不为空removeEldestEntry定义哪些情况下需要进行移除操作
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            //移除头节点 如果按照开启访问顺序来排序的化,尾节点其实是最新被访问到的节点
            removeNode(hash(key), key, null, false, true);
        }
    }

    /**
     *由使用者自定义的策略 默认为false
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

    

 

1.5:LinkedHashMap中afterNodeRemoval()方法描述:

    /**
     *该方法将双向链表中的某节点移除(包含首尾节点)
     */
    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

2:LinkedHashMap中get(key)源码描述:

LinkedHashMap重写了get方法如下图所示

 //获取数据
 public V get(Object key) {
        Node<K,V> e;

        if ((e = getNode(hash(key), key)) == null)
            return null;
        //如果设置为访问顺序来进行后续迭代
        if (accessOrder)
            //将当前节点移动到双向链表最后一位
            afterNodeAccess(e);
        return e.value;
    }

3:LinkedHashMap中迭代源码描述

LinkedHashMap也重写了,keySet(),values(),entrySet(),forEach(),内部迭代器等一些列和循环迭代的方法

    /**
      *获取linkedHashMap所有key
      */
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new LinkedKeySet();
            keySet = ks;
        }
        return ks;
    }
     
    final class LinkedKeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<K> iterator() {
            return new LinkedKeyIterator();
        }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator()  {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED |
                                            Spliterator.DISTINCT);
        }
        //核心代码
        public final void forEach(Consumer<? super K> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            //此处迭代遍历双向链表 获取节点执行对应操作
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.key);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    /**
      *获取linkedHashMap所有values
      */
    public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new LinkedValues();
            values = vs;
        }
        return vs;
    }

    final class LinkedValues extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<V> iterator() {
            return new LinkedValueIterator();
        }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED);
        }

        public final void forEach(Consumer<? super V> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            //此处迭代遍历双向链表 获取节点执行对应操作
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.value);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    /**
      *获取linkedHashMap所有Map中Entry
      */
    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }

    final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new LinkedEntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED |
                                            Spliterator.DISTINCT);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
           //此处迭代遍历双向链表 获取节点执行对应操作
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    // Map overrides

    public void forEach(BiConsumer<? super K, ? super V> action) {
        if (action == null)
            throw new NullPointerException();
        int mc = modCount;
        //此处迭代遍历双向链表 获取节点执行对应操作
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            action.accept(e.key, e.value);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }

    。。。。。

4:总结:

从上述代码可以发现这几种遍历方式都有一个公共的代码操作:该方法从头到尾会遍历整个双向链表

        public final void forEach(。。。。。。) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            //此处迭代遍历双向链表 获取节点执行对应操作
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                。。。。。。
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }

综上所述LinkedHashMap正是因为HashMap与双向链表的存在,即可以使用HashMap提供的功能,也可以通过两种迭代方式进行循环遍历应对不同的需求场景(对于LinkedHashMap的迭代从上述源码中可以看出实际是对于双向链表的迭代,起为head尾为tail):

第一种:按照插入的顺序迭代(设置属性accessOrder=false),双向链表中节点按照是插入的顺序保存

第二种:按照访问的顺序迭代(设置属性accessOrder=true),每次对该节点进行access操作都会使该节点移动到双向链表的尾部,所以基于此特点在此双向链表尾部的数据都是最近被访问的。

LinkedHashMap还提供了一种淘汰数据的机制(由afterNodeInsertion方法提供),该机制在满足一定条件情况(该条件由使用者定义,继承LinkedHashMap重写removeEldestEntry()方法)下触发。一旦达到触发条件,那么每进行一个写入操作都会移除双向链表head节点(因为head的数据是最早写入的也就是最早存在的这也是removeEldestEntry方法名的含义“移除年老实体”)。LinkedHashMap实现LRU算法的途径之一。

5:Demo代码

 public static void main(String[] args) throws Exception {
        
        //正常按照插入的顺序迭代
        LinkedHashMap<String,Integer> linkedHashMapSort = new LinkedHashMap(6,0.75f,false);

        linkedHashMapSort.put("张三",1);
        linkedHashMapSort.put("李四",2);
        linkedHashMapSort.put("王五",3);
        linkedHashMapSort.put("李六",4);
        linkedHashMapSort.put("赵七",5);
        linkedHashMapSort.get("张三");
        linkedHashMapSort.get("李六");

        //
        linkedHashMapSort.forEach((k,v)->{
            System.out.println(k+"------------"+v);
        });

        System.out.println("----------------------------分割线--------------------------");

        //按照访问的顺序迭代
        LinkedHashMap<String,Integer> linkedHashMapAccessSort = new LinkedHashMap(6,0.75f,true);

        linkedHashMapAccessSort.put("张三",1);
        linkedHashMapAccessSort.put("李四",2);
        linkedHashMapAccessSort.put("王五",3);
        linkedHashMapAccessSort.put("李六",4);
        linkedHashMapAccessSort.put("赵七",5);
        linkedHashMapAccessSort.get("张三");
        linkedHashMapAccessSort.get("李六");

        //
        linkedHashMapAccessSort.forEach((k,v)->{
            System.out.println(k+"------------"+v);
        });

    }

最终结果:

6:应用

LRU算法中应用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值