如无特殊说明,本文以jdk7为准进行说明。
package java.util;
import java.io.*;
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
}
可以看到LinkedHashMap继承了HashMap,那么LinkedHashMap又有什么特点呢?
LinkedHashMap是Hash表和链表的实现,并且依靠着双向链表保证了迭代顺序是插入的顺序。非线程安全。
首先来段代码(与HashMap那篇类似):
Map<String,Integer> map = new LinkedHashMap<>();
map.put("s1", 1);
map.put("s2", 2);
map.put("s3", 3);
map.put("s4", 4);
map.put("s5", 5);
map.put(null, 9);
map.put("s6", 6);
map.put("s7", 7);
map.put("s8", 8);
map.put(null, 11);
for(Map.Entry<String,Integer> entry:map.entrySet())
{
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println(map);
输出结果:
s1:1
s2:2
s3:3
s4:4
s5:5
null:11
s6:6
s7:7
s8:8
{s1=1, s2=2, s3=3, s4=4, s5=5, null=11, s6=6, s7=7, s8=8}
通过结果可以看到,LinkedHashMap会记录插入的顺序,允许null的键值,当key值重复时,后面的会替换前面的。
通过工作看下LinkedHashMap的结构图(ppt画图不容易,求点赞~)
可以看到LinkedHashMap比HashMap多了一个头指针head(private权限),header指针是一个标记指针不存储任何数据。标记after和before两个指针。
可以看到bullet的实体Entry也比HashMap的Entry多了before和after两个指针。(private static class Entry<K,V> extends HashMap.Entry<K,V>{Entry<K,V> before, after;} )
LinkedHashMap还有一个私有变量accessOrder(private final boolean accessOrder;),默认为false,即按照插入顺序遍历,譬如开篇的例子中,如果设置为true则按照访问顺序遍历,只能通过这个构造函数设置:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
接下来举个例子(和开篇的例子类似):
Map<String,Integer> map = new LinkedHashMap<>(16,0.75f,true);
map.put("s1", 1);
map.put("s2", 2);
map.put("s3", 3);
map.put("s4", 4);
map.put("s5", 5);
map.put(null, 9);
map.put("s6", 6);
map.put("s7", 7);
map.put("s8", 8);
map.put(null, 11);
map.get("s6");
for(Map.Entry<String,Integer> entry:map.entrySet())
{
System.out.println(entry.getKey()+":"+entry.getValue());
}
输出结果:
s1:1
s2:2
s3:3
s4:4
s5:5
s7:7
s8:8
null:11
s6:6
可以看到遍历顺序改为了访问顺序。这个功能有什么牛逼之处?提示一下:LRU。
LinkedHashMap提供了一个protected的方法:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
LinkedHashMap中的put方法直接继承HashMap的put方法,并没有重写,但是put方法中需要用到addEntry方法,并且LinkedHashMap对其进行了重写如下:
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;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
方法中调用了removeEldestEntry()方法,如果重写这个方法就可以实现LRU。
譬如:
package collections;
import java.util.Map;
public class LRUCache {
private int capacity;
private Map<Integer, Integer> cache;
public LRUCache(final int capacity) {
this.capacity = capacity;
this.cache = new java.util.LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
// 定义put后的移除规则,大于容量就删除eldest
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
if (cache.containsKey(key)) {
return cache.get(key);
} else
return -1;
}
public void set(int key, int value) {
cache.put(key, value);
}
}
引用《关于Java集合的小抄》来做一下总结:
扩展HashMap增加双向链表的实现,号称是最占内存的数据结构。支持iterator()时按Entry的插入顺序来排序(但是更新不算, 如果设置accessOrder属性为true,则所有读写访问都算)。
实现上是在Entry上再增加属性before/after指针,插入时把自己加到Header Entry的前面去。如果所有读写访问都要排序,还要把前后Entry的before/after拼接起来以在链表中删除掉自己。
参考资料:
1. 《关于Java集合的小抄》