LinkedHashMap

作者: 淡季的风 | 来源:发表于2020-07-24 18:27 被阅读0次

    LinkedHashMap

    • LinkedHashMap继承自HashMap, 采用数组+双向链表实现。
    • LinkedHashMap有序, HashMap有序。 LinkedHashMap的有序分为插入顺序和访问顺序, 默认采用插入顺序。
    • LinkedHashMap依然采用HashMap的数组+单向链表存储数据, 双向链表只是用来保证顺序。
    • LinkedHashMap并没有重写HashMap的底层数据结构和几个重要方法, 而是采用钩子技术, 在不影响HashMap本身方法的情况下, 维护一个额外的双向链表。
    • LinkedHashMap 线程不安全。

    数据结构

    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在继承HashMap.Node的基础上, 新增了beforeafter两个字段,代表双向链表的前后两个指针。在LinkedHashMap新增、删除、更新、访问数据的时候,通过操作该双向链表而保证有序。

    • 新增
     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    

    如上代码所示, HashMap的putVal方法中, 存在newNode、afterNodeAccess、afterNodeInsertion 3个重要钩子方法。
    LinkedHashMap分别重写了上述3个方法:

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

    LinkedHashMap重写了newNode方法,每次新增元素时, 调用linkNodeLast方法,将新增元素追加到双向链表的末尾:

    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;
            }
     }
    
    1. afterNodeAccess
    void afterNodeAccess(Node<K,V> e) { // move node to last
            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
                    last = b;
                if (last == null)
                    head = p;
                else {
                    p.before = last;
                    last.after = p;
                }
                tail = p;
                ++modCount;
            }
        }
    

    LinkedHashMap每次更新数据或访问数据时, 调用afterNodeAccess方法,将当前元素从双向链表中先删除, 再追加志链表末尾。afterNodeAccess方法仅在采用访问顺序排序的的情况下被调用。

    1. afterNodeInsertion
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
            LinkedHashMap.Entry<K,V> first;
            if (evict && (first = head) != null && removeEldestEntry(first)) {
                K key = first.key;
                removeNode(hash(key), key, null, false, true);
            }
        }
    

    LinkedHashMap不产生作用。

    • 删除
    final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                else if ((e = p.next) != null) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    --size;
                    afterNodeRemoval(node);
                    return node;
                }
            }
            return null;
        }
    

    如上代码所示, HashMap的removeNode方法中, 存在afterNodeRemoval钩子方法,LinkedHashMap通过重写该方法, 在删除数据时,更新双向链表:

    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;
        }
    
    • 访问
      LinkedHashMap默认采用插入顺序。采用访问顺序时,通过afterNodeAccess钩子方法,保证有序。
    • 遍历
    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(); }
        }
    

    LinkedHashMap遍历时, 通过遍历上述的双向链表, 而不是和HashMap一样,遍历数组+单向链表。

    • 扩容

    其他

    • LinkedHashMap可实现LRU Cache。

    相关文章

      网友评论

        本文标题:LinkedHashMap

        本文链接:https://www.haomeiwen.com/subject/qtrslktx.html