首先看继承结构
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
可以看到LinkedHashMap继承自 HashMap.
成员变量
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;
有头节点,尾节点,及是否按访问顺序的布尔值。
该accessOrder被final修饰,即不可改变。
需要注意的是,Entry即节点并是从HashMap中Node继承过来的。我们看下这个Entry
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);
}
}
可以看到该节点相比父类增加了before,after两个节点。
构造方法
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
几种构造方法跟hashmap基本一致,无非是如果不传入了accessOrder,则默认置此常量为false。
几个主要方法
增:LinkedHashMap并没有重写put方法,只是实现了hashmap中几个方法, 如:afterNodeInsertion(),fterNodeAccess()等
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);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
evict 在hashmap默认是true。removeEldestEntry总返回false,所以一直此函数什么都没做。
那么插入是有序必然是在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;
}
}
果然,可以看到该方法中维持一个双向链表,将最新加入的节点,放在该链表末尾。那么取数据只需要按照此链表遍历即可,就实现了按添加顺序为查询顺序的有序链表。
看下遍历方法
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
如我们所想,遍历该双向链表。
如果是按访问顺序呢,是怎么实现的?
我们看下get方法
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;
}
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;
}
}
可以看到是将该节点放到了双向链表的结尾。
面试中经常问到的相关问题:
1.说一下LinkedHashMap的数据结构
LinkedHashMap继承自HashMap,并维持了一个双向链表。插入节点时,将节点追加到双向链表尾部,从而实现按照插入顺序的有序访问。也可以在初始化LinkedHashMap对象时设定为按照访问顺序排序,此时每当访问一个节点,afternodeaccess方法就会将该节点放到双向链表的尾部,从而实现按照访问顺序的有序遍历访问。
2.如何利用LinkedHashMap实现一个Cache
由于accessOrder为true的LinkedHashMap已实现了最新访问的节点放到双向链表的末尾,所以很方便通过删除链表头部节点实现缓存的lru算法。
具体来说,只需要继承LinkedHashMap类,并重写removeEldestEntry方法,将该方法改为超出一定长度返回true 即可。
如
final int cacheNum;
public LinkedHashMap(int cacheNum) {
super();
cacheNum=cacheNum;
accessOrder = false;
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
if(size >= cacheNum){
return true;
}
return false;
}
以上