与HashMap的异同:同样是基于散列表实现,区别是,LinkedHashMap内部多了一个双向循环链表的维护,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列,
简单地说:LinkedHashMap=散列表+循环双向链表
首先,HashMap的构造方法都要调用一个方法 init(),而LinkedHashMap对这个方法进行了重写:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
-----> init();
}
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
如果已经熟悉过HashMap,那么你一定可以发现,这里的Entry<>已经和HashMap中的Entry内部类不一样了,所以,LinkedHashMap重写了HashMap中最基本的单元Entry:
/**
* LinkedHashMap entry.
*/
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry before, after;
Entry(int hash, K key, V value, HashMap.Entry next) {
super(hash, key, value, next);
}
private void remove() {
before.after = after;
after.before = before;
}
private void addBefore(Entry existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
void recordAccess(HashMap m) {
LinkedHashMap lm = (LinkedHashMap)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap m) {
remove();
}
}
看到这段内部类的代码,我们可以总结出两个重要的特性:
(1)这里的 before 和 after 是增加的。此处的Entry它继承了HashMap.Entry,所以它的成员变量有:
hash值(这个容易被忽略)
key 和 value 两个变量
用于维护哈希表的 next
用于维护双向循环链表的 before 和 after
(2)LinkedHashMap中依然在使用 【桶+链表】 的典型哈希表结构。这一点从它复用的HashMap中的代码可以看出。所以,它依然会有resize,使用的是链表来维护顺序,所以,resize并不会破坏这个顺序的维护。
private final boolean accessOrder;
accessOrder:true则按照LRU算法迭代整个LinkedHashMap,false则按照元素的插入顺序迭代!
最后两个图是精髓,访问就是先删除后添加到尾部。
循环双向链表的头部存放的是最久访问的节点或最先插入的节点,尾部为最近访问的或最近插入的节点
如果上图太简单的话,下面有个例子还是不错的:
public static void main(String[] args) {
LinkedHashMap lMap = new LinkedHashMap(2, 0.3f, true);
for (int i = 0; i < 10; i++) {
lMap.put(i, "@" + i);
}
System.out.println("**** 初始顺序:****");
for (Map.Entry entry : lMap.entrySet()) {
String value = entry.getValue();
System.out.printf("%-5s", value);
}
// 依次访问8,1,4,7,3
lMap.get(8);
lMap.get(1);
lMap.get(4);
lMap.get(7);
lMap.get(3);
System.out.println("\n\n**** 访问过后的顺序:****");
for (Map.Entry entry : lMap.entrySet()) {
String value = entry.getValue();
System.out.printf("%-5s", value);
}
// put 新值
lMap.put(11, "@" + 11);
lMap.put(12, "@" + 12);
lMap.put(13, "@" + 13);
System.out.println("\n\n**** 插入新k-v后的顺序:****");
for (Map.Entry entry : lMap.entrySet()) {
String value = entry.getValue();
System.out.printf("%-5s", value);
}
// put 旧值
lMap.put(0, "new" + 0);
lMap.put(2, "new" + 2);
lMap.put(4, "new" + 4);
System.out.println("\n\n**** 插入旧k后的顺序:****");
for (Map.Entry entry : lMap.entrySet()) {
String value = entry.getValue();
System.out.printf("%-5s", value);
}
}
output:
**** 初始顺序:****
@0 @1 @2 @3 @4 @5 @6 @7 @8 @9
**** 访问过后的顺序:****
@0 @2 @5 @6 @9 @8 @1 @4 @7 @3
**** 插入新k-v后的顺序:****
@0 @2 @5 @6 @9 @8 @1 @4 @7 @3 @11 @12 @13
**** 插入旧k后的顺序:****
@5 @6 @9 @8 @1 @7 @3 @11 @12 @13 new0 new2 new4
实现的原理就是,每次get的时候,将这个元素移到队列的末尾,从而,最不常访问的元素在队列的首部。
参考资料:
http://www.cnblogs.com/chenpi/p/5294077.html
http://blog.csdn.net/cds86333774/article/details/50946990