LinkedHashMap

LinkedHashMap

概述

LinkedHashMap数据结构相比较于HashMap来说,添加了双向指针,分别指向前一个节点——before和后一个节点——after,从而将所有的节点已链表的形式串联一起来

//HashMap里面的方法在LinkedHashMap进行了重写
	void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

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两个指针。

类属性

// 用于指向双向链表的头部
    transient LinkedHashMap.Entry<K,V> head;
//用于指向双向链表的尾部
    transient LinkedHashMap.Entry<K,V> tail;
 * 用来指定LinkedHashMap的迭代顺序,true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾false则表示按照插入顺序来
 */ 
    final boolean accessOrder;

构造方法

多了一个 accessOrder的参数,用来指定按照LRU排列方式还是顺序插入的排序方式。其他的HashMap一样

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

put方法

// 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);
    // 将 Entry 接在双向链表的尾部
    linkNodeLast(p);
    return p;
}
// LinkedHashMap 中实现
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // last 为 null,表明链表还未建立
    if (last == null)
        head = p;
    else {
        // 将新节点 p 接在链表尾部
        p.before = last;
        last.after = p;
    }
}

//LinkedHashMap 中重写
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;
}

//插入后把最老的Entry删除,不过removeEldestEntry总是返回false,所以不会删除
void afterNodeInsertion(boolean evict) {
  LinkedHashMap.Entry<K,V> first;
  if (evict && (first = head) != null && removeEldestEntry(first)) {
    K key = first.key;
    removeNode(hash(key), key, null, false, true);
  }
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  return false;
}

通过重写newNode创建了Entry,在通过linkNodeLast 将Entry放入链表的尾部。实现了双向链表的建立

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

//此函数执行的效果就是将最近使用的Node,放在链表的最末尾
void afterNodeAccess(Node<K,V> e) {
  LinkedHashMap.Entry<K,V> last;
  //仅当按照LRU原则且e不在最末尾,才执行修改链表,将e移到链表最末尾的操作
  if (accessOrder && (last = tail) != e) {
    //将e赋值临时节点p, b是e的前一个节点, a是e的后一个节点
    LinkedHashMap.Entry<K,V> p =
      (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    //设置p的后一个节点为null,因为执行后p在链表末尾,after肯定为null
    p.after = null;
    //p前一个节点不存在,情况一
    if (b == null) // ①
      head = a;
    else
      b.after = a;
    if (a != null) 
      a.before = b;
    //p的后一个节点不存在,情况二
    else // ②
      last = b;
    //情况三
    if (last == null) // ③
      head = p;
    //正常情况,将p设置为尾节点的准备工作,p的前一个节点为原先的last,last的after为p
    else {
      p.before = last;
      last.after = p;
    }
    //将p设置为将p设置为尾节点
    tail = p;
    // 修改计数器+1
    ++modCount;
  }
}
  • 正常情况下:查询的p在链表中间,那么将p设置到末尾后,它原先的前节点b和后节点a就变成了前后节点。

  • 情况一:p为头部,前一个节点b不存在,那么考虑到p要放到最后面,则设置p的后一个节点a为head

  • 情况二:p为尾部,后一个节点a不存在,那么考虑到统一操作,设置last为b

  • 情况三:p为链表里的第一个节点,head=p

如果链表指针修改的话,其实在桶里面的位置还是没有改变的,只是前后指针改变了

remove()

//调用的还是HashMap的。只不过从写了afterNodeRemoval
void afterNodeRemoval(Node<K,V> e) {
  //与afterNodeAccess一样,记录e的前后节点b,a
  LinkedHashMap.Entry<K,V> p =
    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  //p已删除,前后指针都设置为null,便于GC回收
  p.before = p.after = null;
  //与afterNodeAccess一样类似,一顿判断,然后b,a互为前后节点
  if (b == null)
    head = a;
  else
    b.after = a;
  if (a == null)
    tail = b;
  else
    a.before = b;
}

总结

有序但是增加了时间和空间的开销。节点增加了前后指针。迭代顺序有两种,插入顺序和访问顺序,有着HashMap的所有功能。

posted @ 2020-04-07 14:47  无话可说丶  阅读(171)  评论(0编辑  收藏  举报