LinkedHashMap 概述
源码基于jdk 1.7.81
LinkedHashMap 继承自 HashMap,实现了 Map 接口,所以它具有 Map 的所有方法和特性,同时 LinkedHashMap 继承了 HashMap 所有非私有的成员变量方法。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
private static final long serialVersionUID = 3801124242820219131L;
private transient Entry<K,V> header;//头结点
//为true时,按照访问顺序排序,为false时,按照插入顺 序排序。
private final boolean accessOrder;
private static class Entry<K,V> extends HashMap.Entry<K,V> {
Entry<K,V> before, after;//前驱、后继
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
}
LinkedHashMap中 Entry 节点的成员变量
-
final K key
-
V value;
-
Entry<K,V> next
-
final int hash
-
Entry<K,V> before,
-
Entry<K,V> after;
前四个成员变量也就是红色的是从 HashMap 中继承来的,后面两个成员变量是 LinkedList 中独有的,前四个构成了 HashMap的结构,解决哈希冲突,后两个使得集合有序。
LinkedHashMap的构造方法
LinkedHashMap的构造方法基本上都是继承父类 HashMap的,它的参数 accessOrder 一般来说默认为 false,也就是按照插入顺序来排序。
//继承自HashMap(int initialCapacity, float loadFactor);accessOrder 默认为false
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
//继承自 HashMap(int initialCapacity);accessOrder 默认为false
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//继承自HashMap();accessOrder 默认为false
public LinkedHashMap() {
super();
accessOrder = false;
}
//继承自HashMap(Map<? extends K, ? extends V> m);accessOrder 默认为false
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
//accessOrder 自己传入
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkedHashMap 的数据结构
LinkedHashMap 和 HashMap 一样,都是采用数组+链表的形式,不同的是 LinkedHashMap 在这种结构下,又使用 LinkedList 维护了它的插入先后顺序。
双向链表构成了它的插入顺序。
LinkedHashMap的基本操作
put 方法
这个put 方法是继承自 HashMap 的,当集合中已经存在key 键,则覆盖原来的value,这和 HashMap 的操作一样,但当集合中不存在key 键是,增加元素的 addEntry()方法调用的是 LinkedList 重写的方法。
public V put(K key, V value) {
//当 key 为 null
if (key == null)
return putForNullKey(value);
//得到 hash 值
int hash = hash(key.hashCode());
//获得索引
int i = indexFor(hash, table.length);
//遍历索引位置的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果链表中已经存在当前的key,覆盖即可
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果当前链表没有就增加这个键值对到集合中
addEntry(hash, key, value, i);
return null;
}
下面就是addEntry(int hash, K key, V value, int bucketIndex) 方法,其中createEntry(hash, key, value, bucketIndex);方法保证了插入顺序的先后。
/**
* 增加一个元素到链表中
* bucketIndex 索引值
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//把新的键值对按照尾插法插在双向链表的尾部
createEntry(hash, key, value, bucketIndex);
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
//扩容
if (size >= threshold)
resize(2 * table.length);
}
}
createEntry(int hash, K key, V value, int bucketIndex) 先保留了原来索引位置的节点,然后将新的节点放在索引位置,这样保证了HashMap的结构,后面调用addBefore(header)方法将新结点插入到双向链表中去,保证了LinkedList 结构。
void createEntry(int hash, K key, V value, int bucketIndex) {
//原来索引位置存的节点
HashMap.Entry<K,V> old = table[bucketIndex];
//创造一个新的节点,存储待插入的键值对,
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
//把当前的键值对放在索引位置。
table[bucketIndex] = e;
//用尾插法把新结点插在双向链表的尾部
e.addBefore(header);
size++;
}
addBefore(Entry<K,V> existingEntry) 采用尾插法,把新结点插入到了双向链表的尾部,保证了顺序。
/**
* existingEntry 在这里指的是header
* 这里采用的是尾插法,构成的是一个链表
*/
private void addBefore(Entry<K,V> existingEntry) {
//后继为header
after = existingEntry;
//前驱为header 的前驱
before = existingEntry.before;
before.after = this;
after.before = this;
}
get()方法
LinkedList 的 get() 是重写了HashMap 的 get() 方法,LinkedList 的 get() 方法里面增加了判断 accessOrder 的语句,当 accessOrder 为 true 时,就将刚刚访问的节点移动到双向链表的尾部。实现了按照访问顺序排序。
/**
* 根据 key 值来获取value
*/
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
//节点不存在
if (e == null)
return null;
//accessOrder 的判断
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//accessOrder 为true时
if (lm.accessOrder) {
lm.modCount++;
//将当前节点删掉
remove();
//把当前节点放在header 的前面,也就是双向链表的尾部。
addBefore(lm.header);
}
}
remove() 方法
LinkedHashMap 的remove() 方法继承自 HashMap 的 remove() 方法。
//删除元素
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);//得到索引值
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
//LinkedHashMap 重写了这个方法,从双向链表中删除这个节点。
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
e.recordRemoval(this) 方法调用了LinkedHashMap 的remove()方法,在双向链表中删除了这个节点。
示例
public class TestDemo7 {
public static void main(String[] args) {
LinkedHashMap<Integer,Integer> link =
new LinkedHashMap<Integer,Integer>(16,0.75f,true);
link.put(1, 1);
link.put(7, 5);
link.put(3, 4);
link.put(5, 7);
link.put(2, 6);
link.put(9, 0);
System.out.println(link);
link.get(3);
link.get(2);
System.out.println("访问后");
System.out.println(link);
}
结果
{1=1, 7=5, 3=4, 5=7, 2=6, 9=0}
访问后
{1=1, 7=5, 5=7, 9=0, 3=4, 2=6}
总结
LinkedHashMap 是 HashMap 和 LinedList 的结合,HashMap 维护了数据结构,LinedList 维护了插入顺序。
LinkedHashMap 是有序的。
LinkedHashMap 可以按照插入顺序排序,也可以按照访问顺序排序。