探索LinkedHashMap底层实现

前言

印象中对LinkedHashMap的使用次数好像也不多,参考了几篇文章发现它依赖于HashMap而存在,也就是说它在HashMap的基础上又增加了某些特性,比如说有序。既然是基于HashMap,那么它内部的数据结构也应该有数组 + 链表 + 红黑树,而刚才说它又加上了自己的有序特性,那么应该还有一条链表来维护该顺序,如下图所示:

LinkedHashMap数据结构

该图侧重于它是如何保持有序的,就像上面说的是一条链表,画的有点丑,不过应该不难理解。另外一方面,由于它是基于HashMap,所以在看这篇文章之前最好能对HashMap有个了解,加上接下来并不会过多的解释它的数据结构,更多的关注还是在有序及其他特性上,毕竟它的数据结构是偷来的,探索LinkedHashMap底层实现是基于JDK1.8

阅读注释

LinkedHashMap注释-1

提供了一条链表来维护有序性,默认顺序是节点的插入顺序。

LinkedHashMap注释-2

还提供了按照访问顺序进行排序,该排序方式适合左LRU缓存。

LinkedHashMap注释-3

LinkedHashMap的迭代性能比HashMap高,因为HashMap会迭代所有容量,包括某些索引上并没有节点,而LinkedHashMap按照链表来迭代,并不会理会空节点。

LinkedHashMap注释-4

非线程安全,与HashMap相比,结构修改的概念不太一样。

LinkedHashMap注释-5

迭代器会发生快速失败倒是每个集合的共有特点。

数据结构


    //继承了HashMap
    public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {

        //唯一序列号
        private static final long serialVersionUID = 3801124242820219131L;

        //双向链表的头节点,也代表着最少访问
        transient LinkedHashMap.Entry<K,V> head;

        //双向链表的尾节点,也代表着最多访问
        transient LinkedHashMap.Entry<K,V> tail;

        /**
         * 设置排序方式,也是迭代时的顺序
         * true:按照访问顺序,最多访问的节点会更新到尾节点
         * false:按照插入顺序
         */
        final boolean accessOrder;
    }

构造函数


    /**
     * 指定初始容量与加载因子来初始化,按照插入顺序排序
     * @param initialCapacity 指定初始容量
     * @param loadFactor 指定加载因子
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 指定初始容量来初始,按照插入顺序排序
     * @param initialCapacity 指定初始容量
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * 采用默认的初始容量与加载因子来初始化,按照插入顺序排序
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * 将指定结集合添加到表中,按照插入顺序排序
     * @param m 指定集合
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    /**
     * 指定容量与加载因子、排序方式来初始化
     * @param initialCapacity 指定初始容量
     * @param loadFactor 指定加载因子
     * @param accessOrder 指定排序方式-按照插入顺序、访问顺序
     */
    public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

方法说明

以下的方法将不会详细介绍HashMap中的相关方法,只可能会略微提到,读者最好能够对HashMap有一定的了解。

简单方法

    /**
     * 将指定节点添加到尾部
     * @param 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;
        }
    }

    /**
     * 复制节点的关联关系以保持有序
     * HashMap中可能出现链表转换成红黑树、也有可能出现红黑树转换为链表,所以相应的在转换过程中对象的结构也应该发生变化,为了保持有序性,必须复制原有结构的关联关系到新结构上
     * @param src 原结构的节点
     * @param dst 现结构的节点
     */
    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;
    }

    /**
     * 恢复到初始状态
     */
    void reinitialize() {
        super.reinitialize();
        head = tail = null;
    }

    /**
     * 创建普通结构的节点
     * 相当于在HashMap原有节点的基础上又维护了一层有序
     * 添加节点时会调用HashMap#putVal,紧着会调用该方法用来创建新节点
     * @param hash 哈希值
     * @param key 指定键
     * @param value 指定值
     * @param e 下一个节点
     * @return 新节点
     */
    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;
    }

    /**
     * 将红黑树结构的节点的变成普通结构的节点,实际上只是对象变了,其成员属性拷贝原有节点上对应的值
     * HashMap#untreeify时会调用该方法
     * @param p 红黑树结构的节点
     * @param next 下一个节点
     * @return 普通结构的节点
     */
    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;
    }

    /**
     * 创建红黑树结构的节点
     * HashMap#putTreeVal时会调用该方法
     * @param hash 哈希值
     * @param key 指定键
     * @param value 指定值
     * @param next 下一个节点
     * @return 新节点
     */
    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;
    }

    /**
     * 将普通结构的节点变成红黑树结构的节点,实际上只是对象变了,其成员属性拷贝原有节点上对应的值
     * @param p 普通结构的节点
     * @param next 下一个节点
     * @return 红黑树结构的节点
     */
    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;
    }

    /**
     * 移除节点后的动作
     * HashMap#removeNode时会调用该方法
     * 将指定节点移除后应该维护其剩余节点的关联关系
     * @param e 指定节点
     */
    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;
    }

    /**
     * 新增节点后的动作
     * 默认情况下不会移除最少访问的节点
     * HashMap#putVal时会调用该方法
     * @param evict 模式
     */
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) { //可根据覆写removeEldestEntry方法来实现不同策略的缓存机制,默认是不会移除
            K key = first.key;
            removeNode(hash(key), key, null, false, true); //移除最少访问的节点,即头部节点,默认情况下不会调用该方法
        }
    }

    /**
     * 访问节点后的动作
     * 上面我们提到accessOrder = true时会按照访问顺序排序,最多访问的节点会被移动到尾部,即将当前节点移动到尾部,若是accessOrder = false则按照插入顺序排序
     * @param e 当前节点,即访问的节点
     */
    void afterNodeAccess(Node<K,V> e) {
        LinkedHashMap.Entry<K,V> last;
        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 //a = null 说明当前节点是尾部,可是我们在一开始就判断了当前节点不是尾部节点,所以这里的else就不会走到
                last = b;
            if (last == null) //last = tail也不可能为空,因为只有当前节点存在的情况才会调用该方法
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p; //将当前节点当作尾部节点
            ++modCount;
        }
    }

    /**
     * 将键值对分别写入到流中
     * @param s 输出流
     */
    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            s.writeObject(e.key);
            s.writeObject(e.value);
        }
    }

    /**
     * 是否包含指定值
     * @param value 指定值
     * @return 是否包含指定值
     */
    public boolean containsValue(Object value) {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

    /**
     * 指定键获取值
     * 若accessOrder = true 调用该方法时认为是访问节点,同时也认为是结构修改,所以会去调用afterNodeAccess
     * @param key 指定键
     * @return null或值
     */
    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;
    }

    /**
     * 指定键获取值,若不存在指定节点则返回默认值
     * @param key 指定键
     * @param defaultValue 默认值
     * @return 值或默认值
     */
    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
    }

    /**
     * 清空
     */
    public void clear() {
        super.clear();
        head = tail = null;
    }

    /**
     * 是否移除最少访问的节点
     * 可通过覆写该方法来实现不同的策略,比如当缓存达到指定瓶颈时,每添加新节点时就删除掉最少访问的节点
     * @param eldest 最少访问的节点,即头节点
     * @return 是否移除
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

    /**
     * 获取包含所有键的集合
     * @return 包含所有键的Set集合 
     */
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new LinkedKeySet();
            keySet = ks;
        }
        return ks;
    }

    /**
     * 包含所有键的集合
     * 跟HashMap中的代码类似,就不做解释了
     */
    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();
        }
    }

    /**
     * 获取包含所有值的对象
     * @return 包含所有值的对象
     */
    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();
        }
    }

    /**
     * 获取包含所有键值对的集合
     * @return 包含所有键值对的集合
     */
    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();
        }
    }

    /**
     * 遍历表并执行指定动作
     * @param action 指定动作
     */
    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();
    }

    /**
     * 遍历表并执行指定动作后获取新值,利用新值替换所有节点的旧值
     * @param function 指定动作
     */
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        if (function == null)
            throw new NullPointerException();
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            e.value = function.apply(e.key, e.value);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }

    /**
     * 迭代器基类
     */
    abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != 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 = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

    /**
     * 包含所有键的迭代器
     */
    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().getKey(); }
    }

    /**
     * 包含所有值的迭代器
     */
    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    /**
     * 包含所有键值对的迭代器
     */
    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

总结

  • 有序、不可重复、非线程安全:内部通过维护一条双向链表来保证有序性。

  • 排序方式有两种:按插入顺序;按访问顺序,最近访问的节点会被移动到尾部,相当于头节点是最少访问的节点。

  • removeEldestEntry:是否移除最少访问的节点,可覆写该方法来实现不同策略的缓存机制。

重点关注

双向链表保证有序性 可按插入顺序或按访问顺序

posted @ 2020-12-21 21:45  zliawk  阅读(213)  评论(0)    收藏  举报