继承了hashmap,重写了部分方法来实现有序
首先看hashmap 的数据结构
每个元素只跟在相同位置的元素有关系
linkedhashmap 的数据结构
entry 元素 除了有next 指针,还有Before,after 指针 指定前后结点的关系。
新增了一个header 元素,作为entry 双向链表的头结点
要想实现有序,无非是在保存或者遍历元素的时候更新元素在链表中的位置。
public LinkedHashMap() {
super();
accessOrder = false;
}
调用hashmap的构造方法,然后设置了一个accessOrder 属性值。
看下这个属性值的解释:
/**
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
*
* @serial
*/
迭代顺序
true:访问顺序
false:插入顺序
再看父类构造器做了什么:
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
一系列初始化操作,最后调了init方法,该方法在hashmap中实现为空,linkedhashmap 做了实现。看下源码
void init() {
header = new Entry(-1, null, null, null);
header.before = header.after = header;
}
创建了一个双向链表的头结点。
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
如果已经存在相同key的元素,替换之,调用e.recordAccess(this) 方法。
如果table指定位置不存在元素或者有元素但是key不同,调用 addEntry(hash, key, value, i) 方法添加新元素.
接下来看下这两个方法的实现
hashmap中的实现为空,linkedhashmap做了实现。
void recordAccess(HashMap m) {
LinkedHashMap lm = (LinkedHashMap)m;
if (lm.accessOrder) { //如果设置了按照访问顺序遍历
lm.modCount++;
remove();//,删除从链表中删除当前节点
addBefore(lm.header);//将当前元素插入链表末尾
}
}
private void addBefore(Entry existingEntry) {
after = existingEntry; //header
before = existingEntry.before;//header 的before 也就是尾节点
before.after = this;//当前尾节点的后一个节点指向当前节点
after.before = this;//头结点的上一个节点指向当前节点
}
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);//创建元素,将元素添加到链表末尾
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry eldest = header.after;
if (removeEldestEntry(eldest)) {//判断是否要移除最老的元素,实现LRU相关,默认为false
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
createEntry 方法实现
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry old = table[bucketIndex];
Entry e = new Entry(hash, key, value, old);
table[bucketIndex] = e;//插入bucketIndex 位置entry链表的第一位
e.addBefore(header);//插入entry 双向链表尾部
size++;
}
至此我们了解了LinkedHashMap 如何实现元素之间有序:添加元素的同时加入双向链表
文章开头讲到LinkedHashMap有个 accessOrder 可以控制map遍历时是按照访问顺序还是插入顺序 遍历,接下来先看下涉及元素访问的方法get(put方法也算,上面分析过),然后看下LinkedHashMap 的迭代器。
public V get(Object key) {
Entry e = (Entry)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);//调用次方法,前面分析过如果是设置按照访问顺序遍历,将当前元素放入双向列表的尾部,表示最新访问
return e.value;
}
LinkedHashMap 定义了一个内部类LinkedHashIterator,直接遍历双向链表中的元素,从而实现按照访问顺序遍历或者插入顺序遍历
private abstract class LinkedHashIterator implements Iterator {
Entry nextEntry = header.after; //从双向链表的头节点开始遍历
Entry lastReturned = null;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
至此 LinkedHashMap 实现有序遍历的原理分析完毕。接下来看看如何实现一个LRU 缓存
上文提到过linkedhashmap 添加元素的方法 addEntry 中有个判断 是否要删除最老的元素,如果返回true 会删除双向链表中最老的元素,也就是头节点后面的元素。重写removeEldestEntry 即可实现LRU缓存.
例:
public class MyLRUMap extends LinkedHashMap {
private static final int MAX_SIZE = 4;
public MyLRUMap() {
//accessOrder 设置成true
super(16, 0.75F, true);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_SIZE;
}
public static void main(String args[]) {
MyLRUMap map = new MyLRUMap();
map.put("a","1");
map.put("c","2");
map.put("d","3");
map.put("e","4");
map.get("a");
map.get("c");
System.err.println(map);
map.put("f","5");
System.err.println(map);
}
结果
{d=3, e=4, a=1, c=2}
{e=4, a=1, c=2, f=5}