LinkedHashMap是Map 接口的 哈希表(hash table) 和 链表(linked list) 的实现,具有可预测的迭代顺序。此实现与 HashMap 的不同之处在于,它维护一个双链接列表,该列表贯穿其所有条目。这个链表定义了迭代的顺序,通常是键值插入到 map 中的插入顺序(insertion-order). 请注意,如果键重新插入到 map 中,则插入顺序不受影响。
在遍历LinkedHashMap时,不会像遍历 HashMap 和 Hashtable 所得到的遍历顺序是不确定的。而且LinkedHashMap在遍历有顺序保证的同时,不会像TreeMap那样招致额外的开销。它可以用来拷贝已有的map, 不用考虑这个被拷贝的map对象原来的实现方式。拷贝出来的map对象和原始的map对象的顺序是一致的。
void foo(Map m) {
Map copy = new LinkedHashMap(m);
...
}
写一个测试方法跑一下看下结果:
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapTest {
public static void main(String args[]) {
Map<Integer, Integer> map = new HashMap();
for(int i=1; i<5; i++) {
map.put(i, i*i);
}
for(int i=1; i>-5; i--) {
map.put(i, i*i);
}
System.out.println(map);
System.out.println(map);
System.out.println(map);
System.out.println("-----------------copy from map -----------------------");
LinkedHashMap<Integer,Integer> copy = new LinkedHashMap<Integer, Integer>(map);
System.out.println(copy);
Map<Integer, Integer> linkedHashMap = new LinkedHashMap();
for(int i=1; i<5; i++) {
linkedHashMap.put(i, i*i);
}
for(int i=1; i>-5; i--) {
linkedHashMap.put(i, i*i);
}
System.out.println("-----------------------------------------");
System.out.println(linkedHashMap);
System.out.println("---------- put exist key again ---------------------");
for(int i=1; i<5; i++) {
linkedHashMap.put(i, i*i);
}
System.out.println(linkedHashMap);
System.out.println("--- remove exist key and put again, keep insert order ----");
linkedHashMap.remove(3);
linkedHashMap.put(3, 9);
System.out.println(linkedHashMap);
}
}
输出内容:
{0=0, -1=1, 1=1, -2=4, 2=4, -3=9, 3=9, -4=16, 4=16}
{0=0, -1=1, 1=1, -2=4, 2=4, -3=9, 3=9, -4=16, 4=16}
{0=0, -1=1, 1=1, -2=4, 2=4, -3=9, 3=9, -4=16, 4=16}
-----------------copy from map -----------------------
{0=0, -1=1, 1=1, -2=4, 2=4, -3=9, 3=9, -4=16, 4=16}
-----------------------------------------
{1=1, 2=4, 3=9, 4=16, 0=0, -1=1, -2=4, -3=9, -4=16}
---------- put exist key again ---------------------
{1=1, 2=4, 3=9, 4=16, 0=0, -1=1, -2=4, -3=9, -4=16}
--- remove exist key and put again, keep insert order ----
{1=1, 2=4, 4=16, 0=0, -1=1, -2=4, -3=9, -4=16, 3=9}
如果模块在输入上获取映射,将其复制并随后返回结果(其顺序由副本的顺序确定),则此技术特别有用。(客户通常喜欢按插入的顺序返回。)
LinkedHashMap 有一个特殊的构造函数:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
这个构造函数的参数除了有初始化容量 initialCapacity 和加载因子 loadFactor 之外,还有一个布尔参数:accessOrder
accessOrder指定了维护顺序的模式。
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapTest {
public static void main(String args[]) {
Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(10, 0.75f, true);
for(int i=0; i<10; i++) {
map.put(i, i*i);
}
System.out.println("------------origin order---------");
System.out.println(map);
System.out.println(map.values());
for(int i=1; i<10; i++) {
map.get(3);
}
for(int i=1; i<30; i++) {
map.get(6);
}
for(int i=1; i<10; i++) {
map.get(4);
}
for(int i=1; i<9; i++) {
map.get(2);
}
System.out.println("------------ after access ---------");
System.out.println(map);
System.out.println(map.values());
}
}
输出内容:
------------origin order---------
{0=0, 1=1, 2=4, 3=9, 4=16, 5=25, 6=36, 7=49, 8=64, 9=81}
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
------------ after access ---------
{0=0, 1=1, 5=25, 7=49, 8=64, 9=81, 3=9, 6=36, 4=16, 2=4}
[0, 1, 25, 49, 64, 81, 9, 36, 16, 4]
它的移动算法如下:
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;
}
}
如果此 Map 应删除其老的条目,则返回 true。 此方法在将新条目插入映射后,通过 put 和 putAll 调用。 它为实现者提供了每次添加新条目时删除最老条目的机会。 如果映射表示缓存,这非常有用:它允许Map通过删除陈旧条目来减少内存消耗。
写一个代码测试一下:
import java.util.LinkedHashMap;
import java.util.Map;
public class CacheMap<K, V> extends LinkedHashMap<K, V> {
public static final int MAX_SIZE = 10;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> entry) {
return size() > MAX_SIZE;
}
public CacheMap(int i, float v, boolean b) {
super(i, v, b);
}
public static void main(String[] args) {
CacheMap<Integer, Integer> cacheMap = new CacheMap<Integer, Integer>(40, 0.75f, true);
for (int i=0; i<40; i++) {
cacheMap.put(i, i*i);
}
for (int i=1; i<20; i++) {
cacheMap.get(3);
}
System.out.println(cacheMap);
System.out.println("cacheMap.size() = "+cacheMap.size());
cacheMap.put(20, 400);
System.out.println("-----------after insert new element---------------");
System.out.println(cacheMap);
System.out.println("cacheMap.size() = "+cacheMap.size());
}
}
输出内容:
{30=900, 31=961, 32=1024, 33=1089, 34=1156, 35=1225, 36=1296, 37=1369, 38=1444, 39=1521}
cacheMap.size() = 10
-----------after insert new element---------------
{31=961, 32=1024, 33=1089, 34=1156, 35=1225, 36=1296, 37=1369, 38=1444, 39=1521, 20=400}
cacheMap.size() = 10
看到没有,在CacheMap里面,我设置最大容量是10.通过重写removeEldestEntry方法,在里面判断size()和MAX_SIZE的大小决定要不要移除最老的元素。以达到插入新元素时移除老元素的目的。
此方法通常不会以任何方式修改map,而是允许map按照其返回值的指示进行自身修改。此方法允许直接修改map,但如果这样做,则必须返回 false(指示map不应尝试任何进一步修改)。未指定在此方法内修改映map返回 true 的效果。
在LinkedHashMap中,此实现仅返回 false(因此此映射的行为类似于普通地图 - 最长的元素永远不会被删除)。
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//-------------------------------------------------------------------------------------------------
//------------------------------------------- HashMap.Node ----------------------------------------
//-------------------------------------------------------------------------------------------------
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
可以看到,Entry 和Map.Node相比,多了两个属性:before 和 after. 用于维护双向队列。
// 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;
}
```
### 如何替换Entry
```java
// apply src's links to dst
private void transferLinks(LinkedHashMap.Entry<K,V> src,
LinkedHashMap.Entry<K,V> dst) {
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
if (b == null)
head = dst;
else
b.after = dst;
if (a == null)
tail = dst;
else
a.before = dst;
}