LinkedHashMap原理及LRU

基于jdk 1.6 源码分析

1.结构

在这里插入图片描述

继承了hashmap,重写了部分方法来实现有序

2.有序原理

首先看hashmap 的数据结构
在这里插入图片描述

每个元素只跟在相同位置的元素有关系

linkedhashmap 的数据结构
在这里插入图片描述

entry 元素 除了有next 指针,还有Before,after 指针 指定前后结点的关系。
新增了一个header 元素,作为entry 双向链表的头结点

3.源码分析

要想实现有序,无非是在保存或者遍历元素的时候更新元素在链表中的位置。

3.1 首先看构造方法
    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;
     }
创建了一个双向链表的头结点。
3.2 看 hashmap 的put方法
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) 方法添加新元素.
       接下来看下这两个方法的实现
3.3 LinkedHashMap Entry.recordAccess 方法

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;//头结点的上一个节点指向当前节点
   }
3.4 LinkedHashMap addEntry 方法
 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 的迭代器。

3.5 LinkedHashMap get 方法
public V get(Object key) {
        Entry e = (Entry)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);//调用次方法,前面分析过如果是设置按照访问顺序遍历,将当前元素放入双向列表的尾部,表示最新访问
        return e.value;
 }
3.6 LinkedHashMap LinkedHashIterator

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 缓存

4.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}

你可能感兴趣的:(JAVA集合)