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: <tt>true</tt>
 * for access-order, <tt>false</tt> 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<K,V>(-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<K,V> 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<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) { //如果设置了按照访问顺序遍历
                lm.modCount++;
                remove();//,删除从链表中删除当前节点
                addBefore(lm.header);//将当前元素插入链表末尾
            }
   }
   
    private void addBefore(Entry<K,V> 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<K,V> 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<K,V> old = table[bucketIndex];
	Entry<K,V> e = new Entry<K,V>(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<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);//调用次方法,前面分析过如果是设置按照访问顺序遍历,将当前元素放入双向列表的尾部,表示最新访问
        return e.value;
 }
3.6 LinkedHashMap LinkedHashIterator

LinkedHashMap 定义了一个内部类LinkedHashIterator,直接遍历双向链表中的元素,从而实现按照访问顺序遍历或者插入顺序遍历

private abstract class LinkedHashIterator<T> implements Iterator<T> {
	Entry<K,V> nextEntry    = header.after; //从双向链表的头节点开始遍历
	Entry<K,V> 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<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;
	}
    }

至此 LinkedHashMap 实现有序遍历的原理分析完毕。接下来看看如何实现一个LRU 缓存

4.LRU缓存

  上文提到过linkedhashmap 添加元素的方法 addEntry 中有个判断 是否要删除最老的元素,如果返回true 会删除双向链表中最老的元素,也就是头节点后面的元素。重写removeEldestEntry 即可实现LRU缓存.
例:

public class MyLRUMap<K,V> extends LinkedHashMap<K,V> {
    private static final int MAX_SIZE = 4;

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_SIZE;
    }

    public static void main(String args[]) {
        MyLRUMap<String,String> map = new MyLRUMap();
        map.put("a","1");
        map.put("c","2");
        map.put("d","3");
        map.put("e","4");
        System.err.println(map);
        map.put("f","5");
        System.err.println(map);
    }
}
结果
{a=1, c=2, d=3, e=4}
{c=2, d=3, e=4, f=5}

猜你喜欢

转载自blog.csdn.net/jisuanjixuezi/article/details/88364232
今日推荐