LinkedHashMap的简要介绍
前面我们学了HashMap,考虑这么一种情况,在遍历Map的时候,我们希望遍历的顺序是插入的顺序,这时就要用到LinkedHashMap了。
LinkedHashMap继承自HashMap,其内部维护一个环形双向链表,底层是数组+链表/红黑树,其迭代顺序有插入顺序和访问顺序,默认是插入顺序,如果要使用访问顺序来遍历则需要重写LinkedHashMap的几个方法或者扩展成LRUMap来使用。

LinkedHashMap继承自HashMap,实现了Map接口。
从源码分析LinkedHashMap
成员变量
static class Entry<K,V> extends HashMap.Node<K,V> {//可以看到在HashMap的基础上,维护一个环形双向链表
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
transient LinkedHashMap.Entry<K,V> head;//双向链表的头
transient LinkedHashMap.Entry<K,V> tail;//双向链表的尾
final boolean accessOrder;//这个是访问顺序,默认是false,即插入顺序,如果是true就是访问顺序
构造方法
public LinkedHashMap() {//看这个就够了,直接调用父类的构造方法,设置accessOrder为false,即默认是插入顺序
super();
accessOrder = false;
}
//同HashMap总共有4种构造方法,默认大小为16,负载因子为0.75,以下就不一一列举了
添加
//添加同HashMap的put()方法,但重写了newNode方法
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;
}
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;
}
}
//afterNodeInsertion也重写了
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
删除
//删除同HashMap,但重写了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)//p的before为null说明p是头结点
head = a;
else
b.after = a;
if (a == null)//p的after为null说明p是尾结点
tail = b;
else
a.before = b;
}
查询
public V get(Object key) {//基本同HashMap,但判定是否要accessOrder,如果要的话每次访问后要讲节点放到双向链表最后
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
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;
}
}
遍历
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 contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
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;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)//从链表的头开始foreach
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
//使用到的迭代器
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
总结
这次的LinkedHashMap跟以前学的容器相比,最大的特点就是:很多方法都直接继承了父类,子类重写了父类的部分方法即可达到多态的效果。LinkedHashMap相比较于HashMap的优点是能保持插入顺序(或者访问顺序,实现LRU算法),接下来将要学习的容器是TreeMap,这个容器不需要占用额外的空间(相比较于LinkedHashMap,需要维护一个环形双向链表),但TreeMap的访问时间开销是(O(logn)),而LinkedHashMap的时间开销是(O(1))。