LinkedHashMap源码解析(基于JDK1.8)


LinkedHashMap源码解析(基于JDK1.8)

整体数据结构

  • LinkedHashMap继承自HashMap,实现了Map接口,存储元素的基本结构还是数组与单向链表(jdk1.8版本后,当链表长度大于8时,转换为红黑树存储)的结合,从而支持hash桶读写,当有重复的元素添加时只是修改原来的值。(这部分原理和HashMap原理一致)。

  • LinkedHashMap内所有节点通过双向链表关联,节点的实现是内部类LinkedHashMap.Entry 继承自HashMap.Node,除了Node 本身有的几个属性外,额外增加了beforeafter 用于指向前一个Entry 后一个Entry,以此实现元素之间维持着一条总的双向链表,正是因为这个链表才保证了LinkedHashMap的有序性。

    LinkedHashMap有两个LinkedHashMap.Entry类型的成员变量headtail,分别表示双向链表表头、表尾,方便检索和遍历。LinkedHashMap通过重写entrySet()方法遍历这个双向链表实现有序遍历。

    PS:双向链表以及遍历的具体实现在JDK1.7和JDK1.8具体实现存在差异。

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

    为什么采用双向链表而不是单链表?

    答:双链表除了有一个指向下一节点的指针外,还有一个指向前一节点的指针,可以快速找到前一节点,而单链表无法做到这一点,单链表只能单向读取。双链表的删除的时间复杂度更小。参考双链表的删除和插入的时间复杂度

    1. 单向链表要删除某一节点时,必须要先通过遍历的方式找到前驱节点(通过待删除节点序号或按值查找)。若仅仅知道待删除节点,是不能知道前驱节点的,故单链表的增删操作复杂度为O(n)。
    2. 双链表(双向链表)知道要删除某一节点p时,获取其前驱节点q的方式为 q = p->prior,不必再进行遍历,故时间复杂度为O(1)。而若只知道待删除节点的序号,则依然要按序查找,时间复杂度仍为O(n)。
    3. 单、双链表的插入操作,若给定前驱节点,则时间复杂度均为O(1)。否则只能按序或按值查找前驱节点,时间复杂度为O(n)。
    4. 至于查找,二者的时间复杂度均为O(n)。对于最基本的CRUD操作,双链表优势在于删除给定节点。但其劣势在于浪费存储空间(若从工程角度考量,则其维护性和可读性都更低)。
    5. 双链表本身的结构优势在于,可以O(1)地找到前驱节点,若算法需要对待操作节点的前驱节点做处理,则双链表相比单链表有更加便捷的优势。
  • LinkedHashMap有一个成员变量accessOrder,还有一个三个参数的构造方法

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

    第三个参数accessOrder如果被赋值为true,作用是为了记录访问的顺序的,一个元素节点如果被访问过了之后,这里true说明我们要把被访问过的节点通过afterNodeAccess 方法将结点移动至双向链表尾部。

    LruCache就是通过LinkedHashMap 的这个特性帮我们实现LRU算法,做到最近最少使用的排序。

操作集合

添加元素

调用put方法,即父类HashMapput 方法,即调用 putVal 方法,其中有三个关键方法 newNodenewTreeNode以及afterNodeAccess 。如果不存在key对应的元素,则创建新节点,newNode是新建普通节点,newTreeNode是新建红黑树节点。如果已存在key对应的元素,则将value替换为新值,然后调用afterNodeAccess具体逻辑参照HashMap的putVal方法

LinkedHashMap重写了这三个方法。

创建新节点的两个方法newNodenewTreeNode

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

这两个方法中都调用了linkNodeLast方法,即创建新节点以后,将新节点方法双向链表的尾部。

// link at the end of list
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;
  }
}

已存在key对应的元素,则将value替换为新值。

将value替换为新值以后,调用afterNodeAccess 方法,这个方法的作用是将节点移动至双向链表尾部,属性tail被重新赋值。变量accessOrderLinkedHashMap构造方法传入的。

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;
  }
}
获取元素

调用 get 方法,即调用父类HashMapget 方法,可以看到当 accessOrder 为 true 时,仍然会调用 afterNodeAccess 方法。

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;
}
删除元素

调用 remove 方法,即HashMapremove 方法,即 removeNode 方法,内部调用LinkedHashMap重写的 afterNodeRemoval 方法。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;
}
遍历
public class Test {
    public static void main(String[] args) {
        Map<String, String> hashMap = new LinkedHashMap<String, String>();
        hashMap.put("key1", "value1");
        hashMap.put("key2", "value2");
        hashMap.put("key3", "value3");
        Set<Entry<String, String>> set = hashMap.entrySet();
        Iterator<Entry<String, String>> iterator = set.iterator();
        while(iterator.hasNext()) {
            Entry<String, String> entry = iterator.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            System.out.println("key:" + key + ", value:" + value);
        }
    }
}

LinkedHashMap通过重写entrySet()方法遍历双向链表实现有序遍历。entrySet()方法返回一个LinkedEntrySet对象。

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 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;
  }
  ...省略代码...
}

LinkedEntrySetiterator()方法返回一个LinkedEntryIterator类型的迭代器对象,通过next()方法调用nextNode()方法。

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

LinkedEntryIterator继承了LinkedHashIterator,这里nextNode()方法遍历双向链表获取下一个节点,hasNext()判断是否还有下一个未遍历的节点。

abstract class LinkedHashIterator {
  LinkedHashMap.Entry<K,V> next;//通过next方法接下来即将遍历到的节点
  LinkedHashMap.Entry<K,V> current;//通过next方法指向的当前的节点,主要用来进行remove操作
  int expectedModCount;//用来防止遍历的时候进行了增删操作

  LinkedHashIterator() {
    //这个head是LinkedHashMap的成员变量,
    //初始化时,让next指向head节点,以便在开始遍历时从头到尾逐个遍历。
    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指向当前正在遍历的节点的的after节点。
    //从这里可以看出,整个遍历过程是,从head开始,然后通过after指向下一个节点,一直到尾部tail
    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;
  }
}

参考文章

双链表的删除和插入的时间复杂度

LinkedHashMap源码(JDK1.7)解读

LinkedHashMap及其源码分析(基于JDK1.7)

为什么LinkedHashMap是有序

Android LruCache源码分析,图片缓存算法的实现原理(基于JDK1.7)

LruCache源码解(基于JDK1.7,Android SDK中源码)

用烂的LruCache,你真的完全懂了么?(基于JDK1.8)


如有错误,欢迎指正。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值