Java LinkedHashMap源码解析

签名

 

1
2
3
public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>

 

可以看到,LinkedHashMap是HashMap的一子类,它根据自身的特性修改了HashMap的内部某些方法的实现,要想知道LinkedHashMap具体修改了哪些方法,就需要了解LinkedHashMap的设计原理了。

设计原理

双向链表

LinkedHashMap是key键有序的HashMap的一种实现。它除了使用哈希表这个数据结构,使用双向链表来保证key的顺序

双向链表

双向链表算是个很常见的数据结构,上图中的头节点的prev、尾节点的next指向null,双向链表还有一种变种,见下图

 

环型双向链表
可以看到,这种链表把首尾节点相连,形成一个环。

 

LinkedHashMap中采用的这种环型双向链表,环型双向链表的用途比较多,感兴趣可以看这里:

双向链表这种数据结构,最关键的是保证在增加节点、删除节点时不要断链,后面在分析LinkedHashMap具体代码时会具体介绍,这里就不赘述了。

LinkedHashMap 特点

一般来说,如果需要使用的Map中的key无序,选择HashMap;如果要求key有序,则选择TreeMap。
但是选择TreeMap就会有性能问题,因为TreeMap的get操作的时间复杂度是O(log(n))的,相比于HashMap的O(1)还是差不少的,LinkedHashMap的出现就是为了平衡这些因素,使得

能够以O(1)时间复杂度增加查找元素,又能够保证key的有序性

此外,LinkedHashMap提供了两种key的顺序:

  • 访问顺序(access order)。非常实用,可以使用这种顺序实现LRU(Least Recently Used)缓存
  • 插入顺序(insertion orde)。同一key的多次插入,并不会影响其顺序

源码分析

首先打开Eclipse的outline面版看看LinkedHashMap里面有那些成员

 

LinkedHashMap结构可以看到,由于LinkedHashMap继承自HashMap,所以大部分的方法都是根据key的有序性而重写了HashMap中的部分方法。

 

构造函数

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//accessOrder为true表示该LinkedHashMap的key为访问顺序
//accessOrder为false表示该LinkedHashMap的key为插入顺序
private final boolean accessOrder;
public LinkedHashMap( int initialCapacity, float loadFactor) {
     super (initialCapacity, loadFactor);
     //默认为false,也就是插入顺序
     accessOrder = false ;
}
public LinkedHashMap( int initialCapacity) {
     super (initialCapacity);
     accessOrder = false ;
}
public LinkedHashMap() {
     super ();
     accessOrder = false ;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
     super (m);
     accessOrder = false ;
}
public LinkedHashMap( int initialCapacity,
                      float loadFactor,
                      boolean accessOrder) {
     super (initialCapacity, loadFactor);
     this .accessOrder = accessOrder;
}
/**
  * Called by superclass constructors and pseudoconstructors (clone,
  * readObject) before any entries are inserted into the map.  Initializes
  * the chain.
  */
@Override
void init() {
     header = new Entry<>(- 1 , null , null , null );
     //通过这里可以看出,LinkedHashMap采用的是环型的双向链表
     header.before = header.after = header;
}

 

LinkedHashMap.Entry

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private static class Entry<K,V> extends HashMap.Entry<K,V> {
     // These fields comprise the doubly linked list used for iteration.
     //每个节点包含两个指针,指向前继节点与后继节点
     Entry<K,V> before, after;
     Entry( int hash, K key, V value, HashMap.Entry<K,V> next) {
         super (hash, key, value, next);
     }
     /**
      * Removes this entry from the linked list.
      */
     //删除一个节点时,需要把
     //1. 前继节点的后继指针 指向 要删除节点的后继节点
     //2. 后继节点的前继指针 指向 要删除节点的前继节点
     private void remove() {
         before.after = after;
         after.before = before;
     }
     /**
      * Inserts this entry before the specified existing entry in the list.
      */
     //在某节点前插入节点
     private void addBefore(Entry<K,V> existingEntry) {
         after  = existingEntry;
         before = existingEntry.before;
         before.after = this ;
         after.before = this ;
     }
     /**
      * This method is invoked by the superclass whenever the value
      * of a pre-existing entry is read by Map.get or modified by Map.set.
      * If the enclosing Map is access-ordered, it moves the entry
      * to the end of the list; otherwise, it does nothing.
      */
     void recordAccess(HashMap<K,V> m) {
         LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
         // 如果需要key的访问顺序,需要把
         // 当前访问的节点删除,并把它插入到双向链表的起始位置
         if (lm.accessOrder) {
             lm.modCount++;
             remove();
             addBefore(lm.header);
         }
     }
     void recordRemoval(HashMap<K,V> m) {
         remove();
     }
}

 

为了更形象表示双向链表是如何删除、增加节点,下面用代码加图示的方式

删除节点

 

删除节点上图中,删除的是b节点

 

 

1
2
3
4
private void remove() {
     before.after = after;  //相当于上图中的操作 1
     after.before = before; //相当于上图中的操作 3
}

 

增加节点

 

增加节点上图中的c节点相当于下面代码中的existingEntry,要插入的是x节点

 

 

1
2
3
4
5
6
private void addBefore(Entry<K,V> existingEntry) {
     after  = existingEntry;         //相当于上图中的操作 1
     before = existingEntry.before;  //相当于上图中的操作 3
     before.after = this ;            //相当于上图中的操作 4
     after.before = this ;            //相当于上图中的操作 2
}

 

知道了增加节点的原理,下面看看LinkedHashMap的代码是怎么实现put方法的

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  * This override alters behavior of superclass put method. It causes newly
  * allocated entry to get inserted at the end of the linked list and
  * removes the eldest entry if appropriate.
  */
void addEntry( int hash, K key, V value, int bucketIndex) {
     super .addEntry(hash, key, value, bucketIndex);
     // Remove eldest entry if instructed
     Entry<K,V> eldest = header.after;
     //如果有必要移除最老的节点,那么就移除。LinkedHashMap默认removeEldestEntry总是返回false
     //也就是这里if里面的语句永远不会执行
     //这里removeEldestEntry主要是给LinkedHashMap的子类留下的一个钩子
     //子类完全可以根据自己的需要重写removeEldestEntry,后面我会举个现实中的例子<p></p>
 
         
         
         <!-- BEGIN #author-bio -->
 
 
<!-- END #author-bio -->
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值