LinkedHashMap

LinkedHashMap

  1. LinkedHashMap继承HashMap,其结构与HashMap相似
  2. 它保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。

我们知道HashMap有一个问题,就是它的遍历顺序是不确定的。但是有些场景,我们需要一个有序的Map。为解决这一问题,LinkedHashMap诞生了。它通过维护一个运行于所有条目的双向链表,虽然增加了时间和空间上的开销,但是保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。

源码分析

LinkedHashMap继承HashMap

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

属性

    transient LinkedHashMap.Entry<K,V> head;//指向最久访问的节点或最先插入的节点
    
    transient LinkedHashMap.Entry<K,V> tail;//指向最近访问的或最近插入的节点

    final boolean accessOrder;//是否基于访问排序

head指向最久访问的节点或最先插入的节点,tail指向最近访问的或最近插入的节点。accessOrder表示是否基于访问排序。默认为false,即插入顺序。

构造器

 public LinkedHashMap(int initialCapacity, float loadFactor) {
       // 调用HashMap的构造方法,其实就是初始化Entry[] table
        super(initialCapacity, loadFactor);
       // 这里是指是否基于访问排序,默认为false
        accessOrder = false;
    }

    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
    
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

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

可以看到LinkedHashMap的构造器就是调用HashMap的构造方法,初始化Node<K,V>[] table。然后设置是否基于访问排序。

内部类Entry

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

我们可以看到Entry<K,V> 继承了HashMap.Node,并额外定义了两个Entry<K,V> before, after;指针,before、after是用于维护Entry插入的先后顺序的。(before指向在它之前插入的entry,afte指向在它之后插入的entry)

那么before、After是如何维护Entry插入的先后顺序的呢?

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

原来LinkedHashMap重写了newNode方法。该方法new了一个entry然后调用了 linkNodeLast( p )方法。

 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

inkNodeLast( p )方法,使before指向在它之前插入的entry,使在它之前插入的entry的after的指向它自己。

下面我们来看看它是如何遍历的。
让我们看看它的迭代器:LinkedHashIterator

abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
        // next 指向head(最久访问的节点或最先插入的节点)
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            // next 指向after(在它之后插入的entry)
            next = e.after;
            return e;
        }

LinkedHashIterator构造器的作用就是使next指向head(最久访问的节点或最先插入的节点)。
nextNode()的作用就是使next 指向after(在它之后插入的entry)。并返回当前的entry。
总结:当我们调用迭代器或增强for循环遍历LinkedHashMap时,首先初始化LinkedHashIterator使next指向head。然后再调用nextNode()依次遍历它的下一个,直到遍历完。

从上面我们可以看到它的迭代器,遍历的是插入顺序,那么它使如何实现访问顺序的呢?
首先看下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;
    }

我们可以看到,除了获取到对应的key外,如果accessOrder设置为true,那么就会调用afterNodeAccess(Node<K,V> e)。

 void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        //当accessOrder为true,且当前节点不是最后一个插入的节点。
        if (accessOrder && (last = tail) != e) {
        //将当前节点的引用赋给p,b指向在p插入之前的节点,a指向在p之后插入的节点。
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
               
            p.after = null;
            //b == null说明当前节点是第一个插入的节点,使 head指向第二个插入的节点。
            if (b == null)
                head = a;
            else
           // 使它前一个的after指向它的后一个。
                b.after = a;
            if (a != null)//说明它不是最后插入的节点
           // 使它后一个的before指向它的前一个。
                a.before = b;
            else
            //使last指向它的前一个
                last = b;
            if (last == null)
                head = p;
            else {
            //将当前节点放置在链表的尾部。
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

afterNodeAccess(Node<K,V> e)就是将你访问的该节点移动到对应桶中链表的尾部。然后你再遍历的时候,尾部就是最近访问的元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值