生活
按代码行数来评估软件开发的进度,就如同按重量来评估飞机建造的进度。
前言
之前学习过HashMap,它是一个无序的kv集合,底层通过数组+链表(JDK8:数组+链表+红黑树)实现,今天要学习的叫做LinkedHashMap,这是一个特殊的HashMap,他继承自HashMap,在HashMap的基础上又给每个entry增加了前驱和后继,使之成为一个有序的HashMap,而且可以通过某个参数的设定,实现LRU(最近最少使用)。
成员
LinkedHashMap特有的两个成员如下:
//头结点
private transient Entry<K,V> header;
//访问顺序 false:按插入顺序 true :按访问顺序
private final boolean accessOrder;
LinkdedHashMap的entry除了继承父类HashMap.Entry以外还加上了前驱和后继,借此使之有序
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkedHashMap的构造器直接调用父类HashMap,
注意HashMap的构造器里有个钩子方法 init()
在HashMap本身是空的,但是在LinkedHashMap下
做了自己这个类特有的事情,就是实例化一个head entry
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
put 方法
put方法用到HashMap的put方法,
1.HashMap的addEntry调用到子类的addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
//里面又调到子类的createEntry
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
//这个方法返回false,不知道干嘛的,以后遇到在看
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
// hashmap 正常的 put
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
//把 自己插入到链表尾部
e.addBefore(header);
size++;
}
//实现把自己插入到链表尾部,
private void addBefore(Entry<K,V> existingEntry) {
// 以e.addBefore(header)为例
// e 的后驱指向 header
after = existingEntry;
//前驱指向head 的前驱 ,其实就是把自己插入到head与之前驱中间
before = existingEntry.before;
//在设置我的前驱的后驱指向我
before.after = this;
//我的后驱的前驱指向我
after.before = this;
}
2.在HashMap的put方法里又有一个钩子方法,用来给子类做自己特有的东西。
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果这个key,有 覆盖value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//执行钩子方法
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果这个key没出现过,直接插入尾部没毛病
addEntry(hash, key, value, i);
return null;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//当选择了根据访问顺序排序时
if (lm.accessOrder) {
lm.modCount++;
//先把自己出链表
remove();
//再把自己插入到尾部
addBefore(lm.header);
}
}
get方法
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
//get方法同样调用这个方法,在访问过后,将entry放置尾部
e.recordAccess(this);
return e.value;
}
迭代器核心方法
迭代器细节不去看了,迭代器的next方法调用到这个,可以看到他是
按照顺序去遍历的,而这个顺序就是LinkedHashMap里的entry的
before和after所决定了,可以通过指定accessOrder为true,在每次get
以及通过put覆盖key时把entry放置在最后,实现LRU
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}