JDK类库源码分析系列3--集合类分析(9) LinkedHashMap

396 阅读6分钟

一、结构

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

LinkedHashMap继承于HashMap,不过其不同的是在遍历的时候LinkedHashMap是可以按添加的顺序去遍历,而HashMap我们通过前面的梳理可以知道其是按数组&链表去遍历,并不能按添加的顺序去遍历,这里的关键是LinkedHashMap去等待Node节点添加了两个变量before&after用来表明这个节点前面的节点与后面的节点。当增删改查的时候通过HashMap对应的3个空方法,去进行对应节点的整理,这部分的整理其是是类似与我们前面梳理的LinkeList。

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

二、变量

1、head

transient LinkedHashMap.Entry<K,V> head;

​ 这个表示头节点。

2、tail

transient LinkedHashMap.Entry<K,V> tail;

尾结点。

3、accessOrder

final boolean accessOrder;

​ 这个是用于迭代遍历的顺序:如果为true,表明的是按存取顺序,为false表明的是插入顺序。整理怎么理解呢,例如我们前面梳理HashMap的putVal方法,在添加的时候可能已经存在了这个元素,所以本次是去覆盖原来的value,这时候算一种存取顺序,如果还没有这个元素,算是一种插入顺序。在存取顺序accessOrder = true中,会将该节点重新将其从原来的位置插入到最后面。而如果是插入顺序,并不会将这个元素调整到最后,还是在原来的位置。

三、构造方法

1、LinkedHashMap(int initialCapacity, float loadFactor)

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

​ 其是直接调用的super方法初始化,同时accessOrder默认为false,表明是插入顺序。

2、LinkedHashMap(int initialCapacity)

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

​ 调用的父类方法。

3、LinkedHashMap()

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

4、HashMap(Map<? extends K, ? extends V> m)

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

​ 这些构造方法可以看到其是调用的父类方法,其主要是将accessOrder设置为默认的false。

四、内部类

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

​ 继承HashMap.Node,其主要是在原继承上增加了before&after节点来表明本节点的上一个节点及下一个节点,借此就能实现按插入的顺序去遍历了。

2、LinkedKeySet

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

​ 与HashMap的没有什么主要 的区别。

3、LinkedValues

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

其他的LinkedEntrySet这些就不再提了。

5、LinkedHashIterator

1)、结构&成员变量&构造方法

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

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

​ 这个就是LinkedHashMap实现的遍历迭代器。next表示下一个节点,current表示当前遍历迭代节点。同时这里是以head首节点为next起点,而HashMap这里是需要遍历数组按顺序看第一个不为空的index是哪个再赋值给next。

2)、方法

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;
    removeNode(p.hash, p.key, null, false, false);
    expectedModCount = modCount;
}

​ 可以看到这里的nextNode是通过取e也就是next的after节点作为下一个next,这里与HashMap的遍历也是不同的。

6、LinkedKeyIterator

final class LinkedKeyIterator extends LinkedHashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().getKey(); }
}

​ 迭代器对节点的key遍历获取。

7、LinkedValueIterator

final class LinkedValueIterator extends LinkedHashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

​ 迭代器对节点的value遍历获取。

8、LinkedEntryIterator

final class LinkedEntryIterator extends LinkedHashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

​ 对节点(Entry | Node)进行遍历,可以看到其只是将nextNode包装为next方法。

​ 这几个与前面的HashMap是类似的。

五、方法

​ 下面我们做来看下其对HashMap的afterNodeAccess&afterNodeInsertion&afterNodeRemoval这三个方法的实现。

1、afterNodeInsertion(boolean evict)

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

​ 这个是在传入元素后调用,例如:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    ......
    afterNodeInsertion(evict);
    return null;
}

​ 然后这个方法还有另外一个功能,就是移除最早添加的元素也就是head节点。要移除其是有两个判断决定的即入参&removeEldestEntry方法,这个方法在LinkedHashMap总是返回false就是不执行删除首节点的操作

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

​ 所以需要自己去重写,这里看JDK的介绍,例如你只想再Map中存储100个节点,就可以重写这个方法来判断删除首节点。

2、afterNodeRemoval(Node<K,V> 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;
}

​ 这个方法就是在进行删除操作后调用的方法,例如:

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    ......
       afterNodeRemoval(node);
       return node;
    ......
}

其的操作类似与LinkedList的操作,连接e也就是本次删除的元素,将其前节点与后节点想连,也就删除了e节点本身。

3、afterNodeAccess(Node<K,V> e)

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

​ 这里就是我们前面解释的accessOrder的使用,其的调用例如:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
       ......
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
       ......
}

​ 已经存在了原来的节点e,就调用afterNodeAccess方法。同时accessOrder为true,表明使用存取顺序。就将本节点e给移到最后面去。其的操作时将p也就是e的前后节点相连,然后再将p设置为尾节点tail = p

4、newNode(int hash, K key, V value, Node<K,V> e)

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

​ 这里就是创建LinkedHashMap的新节点。其主要是调用linkNodeLast方法来将建立的新节点添加到最后。

5、linkNodeLast(LinkedHashMap.Entry<K,V> 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;
    }
}

​ 设置p节点为tail尾节点,然后将tail尾节点设置为p的前置节点,将p设置为tail的后置节点。

5、get(Object key)

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对应的value,可以看到在获取的时候,如果accessOrder为true,也是会将查询到的节点移到最后面。这里其实与前面提到的afterNodeInsertion方法再重写removeEldestEntry方法其实就能实现一个按"最近最少使用"原则去删除节点了。

6、containsValue

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

​ 看是否含有此value,可以看到其的遍历是用的heade.after遍历。