LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序(HashMap是基于散列表实现的,相关HashMap的内容可以看《Java集合类》和《HashMap源码分析》)。
1 public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
LinkedHashMap继承自HashMap并实现了Map接口。
LinkedHashMap只定义了两个属性:
1 /**
2 * The head of the doubly linked list.
3 * 双向链表的头节点
4 */
5 private transient Entry<K,V> header;
6 /**
7 * The iteration ordering method for this linked hash map: true
8 * for access-order, false for insertion-order.
9 * true表示最近最少使用次序,false表示插入顺序
10 */www.zzzyk.com
11 private final boolean accessOrder;
LinkedList一共提供了五个构造方法:
1 // 构造方法1,构造一个指定初始容量和负载因子的、按照插入顺序的LinkedList
2 public LinkedHashMap(int initialCapacity, float loadFactor) {
3 super(initialCapacity, loadFactor);
4 accessOrder = false;
5 }
6 // 构造方法2,构造一个指定初始容量的LinkedHashMap,取得键值对的顺序是插入顺序
7 public LinkedHashMap(int initialCapacity) {
8 super(initialCapacity);
9 accessOrder = false;
10 }
11 // 构造方法3,用默认的初始化容量和负载因子创建一个LinkedHashMap,取得键值对的顺序是插入顺序
12 public LinkedHashMap() {
13 super();
14 accessOrder = false;
15 }
16 // 构造方法4,通过传入的map创建一个LinkedHashMap,容量为默认容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,装载因子为默认值
17 public LinkedHashMap(Map<? extends K, ? extends V> m) {
18 super(m);
19 accessOrder = false;
20 }
21 // 构造方法5,根据指定容量、装载因子和键值对保持顺序创建一个LinkedHashMap
22 public LinkedHashMap(int initialCapacity,
23 float loadFactor,
24 boolean accessOrder) {
25 super(initialCapacity, loadFactor);
26 this.accessOrder = accessOrder;
27 }
从构造方法中可以看出,默认都采用插入顺序来维持取出键值对的次序。所有构造方法都是通过调用父类的构造方法来创建对象的。
LinkedHashMap是基于双向链表的,而且属性中定了一个header节点,为什么构造方法都没有对其进行初始化呢?
注意LinkedHashMap中有一个init()方法, HashMap的构造方法都调用了init()方法,这里LinkedHashMap的构造方法在调用父类构造方法后将从父类构造方法中调用init()方法(这也解释了为什么HashMap中会有一个没有内容的init()方法)。
1 void init() {
2 header = new Entry<K,V>(-1, null, null, null);
3 header.before = header.after = header;
4 }
看init()方法,的确是对header进行了初始化,并构造成一个双向循环链表(和LinkedList的存储结构是一样的)。
transfer(HashMap.Entry[] newTable)方法和init()方法一样也在HashTable中被调用。transfer(HashMap.Entry[] newTable)方法在HashMap调用resize(int newCapacity)方法的时候被调用。
1 void transfer(HashMap.Entry[] newTable) {
2 int newCapacity = newTable.length;
3 for (Entry<K,V> e = header.after; e != header; e = e.after) {
4 int index = indexFor(e.hash, newCapacity);
5 e.next = newTable[index];
6 newTable[index] = e;
7 }
8 }
根据链表节点e的哈希值计算e在新容量的table数组中的索引,并将e插入到计算出的索引所引用的链表中。
containsValue(Object value)
1 public boolean containsValue(Object value) {
2 // Overridden to take advantage of faster iterator
3 if (value==null) {
4 for (Entry e = header.after; e != header; e = e.after)
5 if (e.value==null)
6 return true;
7 } else {
8 for (Entry e = header.after; e != header; e = e.after)
9 if (value.equals(e.value))
10 return true;
11 }
12 return false;
13 }
重写父类的containsValue(Object value)方法,直接通过header遍历链表判断是否有值和value相等,而不用查询table数组。
get(Object key)
1 public V get(Object key) {
2 Entry<K,V> e = (Entry<K,V>)getEntry(key);
3 if (e == null)
4 return null;
5 e.recordAccess(this);
6 return e.value;
7 }
get(Object key)方法通过HashMap的getEntry(Object key)方法获取节点,并返回该节点的value值,获取节点如果为null则返回null。recordAccess(HashMap<K,V> m)是LinkedHashMap的内部类Entry的一个方法,将在介绍Entry的时候进行详细的介绍。
clear()
1 public void clear() {
2 super.clear();
3 header.before = header.after = header;
4 }
clear()方法先调用父类的方法clear()方法,之后将链表的header节点的before和after引用都指向header自身,即header节点就是一个双向循环链表。这样就无法访问到原链表中剩余的其他节点,他们都将被GC回收。
以上的内容多多少少都涉及到了LinkedHashMap的内部类Entry<K,V>,下面详细介绍这个内部类。
1 // 这是一个私有的、静态的内部类,继承自HashMap的Entry。
2 private static class Entry<K,V> extends HashMap.Entry<K,V> {
3 // 对前后节点的引用
4 Entry<K,V> before, after;
5 // 构造方法直接调用父类的构造方法
6 Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
7 super(hash, key, value, next);