1、LinkedHashMap类图
LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
注意,此实现不是同步的,如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。
利用LinkedHashMap可以实现LRU缓存, LinkedHashMap有两大好处:一是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。第二,LinkedHashMap本身提供一个方法removeEldestEntry(eldest: Map.Entry<K, V>): boolean用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashMap相当于一个linkedlist),所以,我们需要override这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。
2、
LinkedHashMap构造实现以及重要方法
LinkedHashMap底层实现如图所示:
1) 构造器
LinkedHashMap提供了5个不同的构造器
//构造一个初始大小为initialCapacity,装载因子为loadFactor的空的LinkedHashMap
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false; //默认链表顺序为插入程序,accessOrder 设置为false
}
// 构造一个初始大小为initialCapacity,装载因子为默认的0.75的空的LinkedHashMap
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//构造一个初始大小为initialCapacity,装载因子为默认的loadFactor的空的LinkedHashMap,链表顺序根据accessOrder确定
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
// 在调用super()构造函数,也就是HashMap的相应构造函
数时,会调用init()方法,,该方法被重写,用于初始化header。
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
2)
Entry元素
LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。
private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); }}3) get(key: Object): V 获取指定Key对应的Value方法 LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。 public V get(Object key) { Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; e.recordAccess(this); return e.value; } void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { //如果是按照获取顺序排序,则移除,然后再添加到链表前端 lm.modCount++; remove(); addBefore(lm.header); } }4) addEntry(hash: int, key: K, value: V, bucketIndex: int): void 向LinkedHashMap中添加元素方法 LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法调用的子方法void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。
void addEntry(int hash, K key, V value, int bucketIndex) { super.addEntry(hash, key, value, bucketIndex); //调用父类HashMap的addEntry()方法 // Remove eldest entry if instructed Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { //如果可能的话,移除最老的元素 removeEntryForKey(eldest.key); } }5) removeEldestEntry(eldest: Map.Entry<K, V>): boolean 重写该方法,可实现移除元素的方法 LinkedHashMap提供了removeEldestEntry(Map.Entry<K,V> eldest)方法,在将新条目插入到映射后,put和 putAll将调用此方法。该方法可以提供在每次添加新条目时移除最旧条目的实现程序,默认返回false,这样,此映射的行为将类似于正常映射,即永远不移除最旧的元素。 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
通过重写removeEldestEntry 方法,实现Lru缓冲。在方法中判断,如果当前容量大于设定的缓冲大小将返回true,将最老的元素移除。注意:实现lru缓冲时,将accessOrder设为true。
@Override
protected boolean removeEldestEntry (Map.Entry eldest) {
return size() > LRUCache.cacheSize;
}