HashMap是无序的,如果希望有序地存储key-value时,就需要使用LinkedHashMap了。
LinkedHashMap的继承关系如下:
LinkedHashMap使用
构造方法
LinkedHashMap有5个构造方法。
| 构造方法参数 | initialCapacity | loadFactor | accessOrder |
|---|---|---|---|
| 空 | 16 | 0.75 | false |
| int initialCapacity | 参数指定 | 0.75 | false |
| int initialCapacity, float loadFactor | 参数指定 | 参数指定 | false |
| int initialCapacity, float loadFactor, boolean accessOrder | 参数指定 | 参数指定 | 参数指定 |
| Map<? extends K, ? extends V> m | 根据m大小 | 0.75 | false |
accessOrder为false表示按照插入顺序,为true表示按照访问顺序。最新插入或访问的节点会被放在链表最后。
get方法
LinkedHashMap在自己的get方法里调用了HashMap的getNode方法,并做了后置处理——如果按照访问顺序排序,就调整顺序。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e); // 后置处理
return e.value;
}
通过entrySet遍历的时候调用的HashMap.Node的方法,不会对LindedHashMap的顺序造成影响。
put已经存在的key也算做访问了这个key。
put方法
LinkedHashMap并没有重写HashMap的put方法,而是重写了newNode方法。
对比一下:
// HashMap
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
// LinkedHashMap
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;
}
new了一个节点,然后用linkNodeLast方法处理,顾名思义将节点连在尾部。
// link at the end of list
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;
}
}
总结
LinkedHashMap是在HashMap的基础上,每个节点上增加了两个指针,分别指向它之前和之后的节点,排序方式由accessOrder决定。
对比一下HashMap和LinkedHashMap的节点数据结构:
// HashMap
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
// 省略方法
}
// LinkedHashMap
static class Entry<K,V> extends HashMap.Node<K,V> { // 继承自HashMap的Mode
Entry<K,V> before, after; // 仅仅多了两个指针
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
注意到这两内部静态类都是默认(空白)访问修饰符,因此遍历的时候只能通过Map.Entry。Map.Entry是内部接口,访问权限是public的。
accessOrder=false时,put的时候将节点连在链表最后面,再次put已有的key不会改变节点顺序。
accessOrder=true时,get的时候修改节点顺序,put已有的key亦会修改节点顺序。
LinkedHashMap实现LRU
重写LinkedHashMap预留的removeEldestEntry方法实现LRU。
public class MyLru {
public static void main(String[] args) {
int size = 3;
Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(size, 1, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
if (super.size() > size) {
return true;
}
return false;
}
};
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 6);
for (HashMap.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry);
}
}
}