009_java.util.LinkedHashMap

继承关系

image.png
继承图上,可以看到这个LinkedHashMap继承整个HashMap,获得HashMap的全部特征,和HashMap相比,除此之外增加的特性是维护节点间的链表关系。而为了实现LinkedHashMap,JDK大叔设计的方式是在HashMap里面放一些钩子函数,当HashMap完成一定的动作后,调用钩子函数做些事情。除此之外,就是重写父类的某些关键的方法。

存储结构

存储结构上来说,LinkedHashMap与HashMap一致,只不过其节点内新增了前后节点的引用:

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引用。

重要的字段

/**
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;

/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial
 */
final boolean accessOrder;

可以看到在LinkedHashMap中,增加了三个自动字段,分别是头结点和尾节点的实例引用。还有布尔值accessOrder来控制LinkedHashMap的行为

构造方法

public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        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几乎没有区别。仅有最后一个构造函数增加了accessOrder进行控制。当accessOrder为true的时候将会使用访问顺序维护链表,这意味着每当访问一个节点上的数据的时候都会被移到链表的尾部。当accessOrder为false的时候,链表顺序将会以插入顺序关联起来,这意味着这个行为总会发生在新节点插入的时候。

钩子方法实现

上文我们知道,LinkedHashMap的设计依赖于钩子方法,这个口子已经在HashMap内已经铺垫了。

节点创建相关方法

HashMap内存在两种节点,一个是普通链表节点,一个是树节点。在LinkedHashMap中,创建的过程需要维护前后节点。

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

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

可以看到,创建节点的时候,都会增加调用linkNodeLast方法的步骤,将插入的节点连接到链表的最后。除此之外,树化与反树化的场合下也需要进行链表维护,其中replacementTreeNode在树化的时候调用,replacementNode在反树化的时候调用。

// 将节点转换为链表节点
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;
}

// 将节点转换为树节点
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中这几个方法都是空实现。

  • afterNodeInsertion(boolean evict) 在节点插入之后做些什么,在HashMap中的putVal()方法中被调用
  • afterNodeAccess(Node<K,V> e)      在节点访问之后被调用,主要在put()已经存在的元素或get()时被调用,如果accessOrder为true,调用这个方法把访问到的节点移动到双向链表的末尾。
  • afterNodeRemoval(Node<K,V> e)     在节点被删除之后触发的方法

afterNodeInsertion源码如下,比较简单,就是对当前节点在链表内的处理。

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 当前场景下 if都为false,就是说,LinkedList默认不进行删除
    // 如果evict 为true,并实现了 在某个条件(比方说容量大于10)下进行删除头节点(最老节点)那么就相当于实现了
    // LRU缓存策略(last recently used)
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 调用删除节点之后将会间接调用 afterNodeRemoval方法
        // afterNodeRemoval会进行维护链表
        removeNode(hash(key), key, null, false, true);
    }
}

// 可以进行覆盖进行判断什么时候进行删除最老节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

// 删除一个节点之后,将会维护链表结构
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;
}

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

afterNodeRemoval方法如下:

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做了自定义的序列化操作,方法如下:

  • internalWriteEntries() 将会被writeObject调用,这属于序列化的部分
  • reinitialize()    将当前状态赋值为初始化状态,将会被clone方法或readObject方法调用
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);
    }
}

void reinitialize() {
    super.reinitialize();
    head = tail = null;
}

// HashMap#reinitialize
void reinitialize() {
    table = null;
    entrySet = null;
    keySet = null;
    values = null;
    modCount = 0;
    threshold = 0;
    size = 0;
}

总结

LinkedHashMap继承自HashMap,其内部增加链表头结点与尾节点,来维护节点的顺序性。在HashMap中存在大量钩子方法提供给LinkedHashMap进行继承并调整了他的行为。

  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值