JDK源码解析之LinkedHashMap的两种排序

概述:

       HashMap 是我们平常项目中经常使用到的集合之一,它存储的是键值对,采用的是 数组 + 链表 + 红黑树的数据结构(详细可看我之前写的一篇关于HashMap的源码的博客),存储的数据是无序的,但是如果我们项目中需要用到有序的HashMap,那么我们可以使用LinkedHashMap,使用归使用, 但是LinkedHashMap 为什么是有序的呢? 接下来就让我们通过源码来回答这问题。

    注:本文使用的是JDK 1.8,主要是解析LinkedHashMap为什么有序相关的源码

源码解析:

      一、LinkedHashMap的数据结构

           
       如图,上面的table部分是在父类HashMap中实现的,下面那部分是链表的数据结构,LinkedHashMap之所以能够有序,也是因为它比 HashMap 多了个链表来存储数据的原因。

      二、LinkedHashMap 的属性、构造方法等
        
           1. 属性
                 
                  
   /**
     * 链表的头节点
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * 链表的尾节点
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * accessOrder为true的话,LinkHashMap中的顺序是最近一次获取的节点放在链表的尾部
     * accessOrder为false的话,LinkHashMap中的顺序跟插入顺序一样
     */
    final boolean accessOrder;
        如上代码,LinkedHashMap 比 HashMap 多了三个属性,其中 head 和 tail 和 LinkedList 头结点和尾节点一样,存放链表的头结点和尾节点的数据,而 accessOrder 则决定LinkedHashMap的排序的方式,这个具体下面会通过源码来解析。

                2. 构造方法
 
    /**
     * 带初始化容量和加载因子
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 带初始化容量
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * 空参数构造方法
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * 传入map赋值
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        // 调用HashMap的方法存入数据
        putMapEntries(m, false);
    }

    /**
     * 带初始化容量和加载因子,还有排序的方式
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
      LinkedHashMap 的构造方法基本都是调用父类 HashMap 的构造方法来进行初始化, 前面4个构造方法都是默认给accessOrder 赋值为false,也就是按照插入顺序来排序的,而最后一个方法可以通过传入 accessOrder 来决定使用哪种方式排序。

             3. 存放数据的内部类         
    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);
        }
    }
       上面代码是 LinkedHashMap 用来存放数据的 Entry,它继承于 HashMap 中的内部类 Node,多增加了 before 属性和 after 属性,用来存当前节点的上一个节点和下一个节点。

      三、LinkedHashMap 两种排序

        1. 按插入顺序排序(当 accessOrder = false)
   
    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;
    }

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

       在上面我们说到 HashMap 中使用 数组 + 链表 + 红黑树 的数据结构,在 HashMap中如果链表的长度超过8个就会转换成红黑树,所以就存在两个创建节点的方法,分别是 创建链表节点的 newNode()  和 创建树节点的 newTreeNode 方法, 而 LinkedHashMap 重写了这两个方法,在创建节点后调用 linkNodeLast方法,将节点的数据也存到自身链表中去。下面是 linkNodeLast 方法的源码。

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    	// 获取当前链表的尾部节点
        LinkedHashMap.Entry<K,V> last = tail;
        // 将尾部节点赋值为 p
        tail = p;
        // 如果之前的尾部为空,说明链表为空,所以将头部也赋值为 p
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }
       2. 按最近获取的数据倒序(当 accessOrder = true)

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

        即使 accessOrder 为 true,LinkedHashMap 中的顺序一开始还是插入的顺序,只有获取元素的时候才会去调用 afterNodeAccess 方法将刚获取的元素放到链表的尾部,下面是 afterNodeAccess方法的源码。

    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        // 如果 accessOrder 为true,并且当前获取的节点不是尾部,将将节点移到尾部
        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;
        }
    }


总结:

      LinkedHashMap 的第二种排序方式是我们在开发中比较少使用,笔者也是看了源码才知道有这种排序,以前也没有发现,不过利用这种排序我们可以实现一个简单的LRU(least recently used 最近最少使用)。


猜你喜欢

转载自blog.csdn.net/qq_36712034/article/details/78231573