集合篇:Map—LinkedHashMap源码解析

1.整体架构

HashMap是无序的,TreeMap可以按照key进行排序,LinkedHashMap可以维护key插入顺序。

LinkedHashMap本身继承HashMap,所以拥有HashMap的全部特性,此外在其基础上提供了两大特性:

  1. 按照插入顺序进行访问
  2. 实现了最先删除最少访问的功能,目的是将很久未被访问的key自动删除

相比较HashMap,LinkedHashMap新增的属性有,

// 继承HashMap的Node节点,增加了before和after两个属性
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

// 通过表头和表尾记录key插入顺序
// 链表头
transient LinkedHashMap.Entry<K,V> head;

// 链表尾
transient LinkedHashMap.Entry<K,V> tail;

// true时,按照访问顺序将经常访问的key放在队尾;false时按照插入顺序进行访问
final boolean accessOrder;

LinkedHashMap的结构很像是将LinkedList的每个元素与HashMap的Node相结合,从而把Map的元素串联起来得到一个链表。链表本身的顺序性维护了插入元素的顺序。

2.源码解析

按顺序新增元素

LinkedHashMap初始化过程中,AccessOrder参数默认为false,即会按照插入顺序提供访问。LinkedHashMap新增节点时使用的是HashMap的put方法,底层运行的也是HashMap的putVal方法。不过对父类putVal方法中调用的newNode方法newTreeNode方法afterNodeAccess方法进行类重写。

newNode方法和newTreeNode方法控制新增节点追加到链表的尾部,这样就保证了插入顺序。

newNode方法源码,

// 父类HashMap中newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

// 子类重写的newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
	// 创建自身的节点类,并连接到链表尾部
    LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// linkNodeLast是LinkedHashMap的方法,用于将节点放置到链表尾
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // 如果last是null,表示链表是空链表。则head和tail都指向p
    if (last == null)
        head = p;
    else {	// 如果链表非空,将p连接到last后面,tail指向p即可
        p.before = last;
        last.after = p;
    }
}

newTreeNode方法与newNode方法一致,区别仅仅是将new的对象改为,

TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);

TreeNode类依旧是HashMap中的TreeNode类,之后newTreeNode方法中会调用linkNodeLast方法将TreeNode放置到当前链表尾部,即this.tail的后面。

按顺序访问元素

LinkedHashMap只提供了单向访问,即按照插入的key的顺序从头到尾对key、value和entry进行访问,而不能像LinkedList一样双向访问。

LinkedHashMap可以通过增强for循环和迭代器的方式对key进行顺序访问,其原理是底层实现的LinkedKeyIterator类、LinkedValueIterator类和LinkedEntryIterator类,以LinkedKeyIterator类为例,

final class LinkedKeyIterator extends LinkedHashIterator implements Iterator<K>

这三个类时LinkedHashMap的内部类,他们的父类LinkedHashIterator是一个定义在LinkedHashMap内部的抽象类。这个抽象类决定了LinkedHashMap能够按照key的插入顺序进行访问,

abstract class LinkedHashIterator {
    LinkedHashMap.Entry<K,V> next;
    LinkedHashMap.Entry<K,V> current;
    int expectedModCount;

    LinkedHashIterator() {
    	// 初始化时,next变量指向LinkedHashMap的head。
        next = head;
        expectedModCount = modCount;
        current = null;
    }

    public final boolean hasNext() {
    	// 判断next是否指向null
        return next != null;
    }

    final LinkedHashMap.Entry<K,V> nextNode() {
        LinkedHashMap.Entry<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        // current变量指向当前节点,更近next为当前节点的after节点
        current = e;
        next = e.after;
        return e;
    }

    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        // 此处删除current指向的当前节点
        current = null;
        K key = p.key;
        removeNode(hash(key), key, null, false, false);
        expectedModCount = modCount;
    }
}

使用这个类,LinkedHashMap定义了三个子类,

final class LinkedKeyIterator extends LinkedHashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().getKey(); }
}

final class LinkedValueIterator extends LinkedHashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

final class LinkedEntryIterator extends LinkedHashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

这三个子类统一调用LinkedHashIterator类的nextNode方法,按顺序得到插入的键值对。在这三个子类的基础上,又创建了相应的3个内部类,LinkedEntrySet、LinkedKeySet和LinkedValueSet类。这三个类用于对迭代器进行操作,分布被LinkedHashMap的entrySet、keySet和valueSet方法调用,创建相应迭代器对象。以LinkedEntrySet类为例,

// LinkedHashMap类entrySet方法,返回LinkedEntryIterator迭代器对象
public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { LinkedHashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
    	// 创建LinkedEntryIterator类对象
        return new LinkedEntryIterator();
    }
    public final boolean contains(Object o) {
    	......
    }
}

迭代器的next操作,实际上就是调用LinkedEntryIterator、LinkedKeyIterator和LinkedValueIterator对象的next方法,该方法中调用父类的nextNode方法得到下一次迭代的节点对象,并依据需求返回entry、key或value。

删除最少访问

这种策略叫做LRU(least recently used),经常访问的元素会被追加到链表尾部,不经常访问的元素会随着链表的改变靠近链表头部。之后通过设置此策略对不常访问的元素进行删除,

@Test
public void lruTest() {
	// 将AccessOrder设置为true
	LinkedHashMap<integer, Integer> map = new LinkedHashMap<Integer, Integer>(4, 0.75, true) {
		put(10, 10);
		put(9, 9);
		put(20, 20);
		put(1, 1);
	}

	@override
	// 复写删除策略,设定当节点数大于3时,开始删除节点
	protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
		return size() > 3;
	}
};

log.info("初始化:{}",JSON.toJSONString(map));
Assert.assertNotNull(map.get(9));
log.info("map.get(9):{}",JSON.toJSONString(map));
Assert.assertNotNull(map.get(20));
log.info("map.get(20):{}",JSON.toJSONString(map));

运行结果,

初始化:{9:9,20:20,1:1}
map.get(9):{20:20,1:1,9:9}
map.get(20):{1:1,9:9,20:20}

当大于3个节点时,最先被添加的头节点(10, 10)被删除,之后对(9, 9)和(20, 20)进行访问,被访问过的节点自动移动至队尾。

转移到队尾的策略

LinkedHashMap中的get方法如下,

public V get(Object key) {
    Node<K,V> e;
    // 父类HashMap的getNode方法
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 当设置AccessOrder为true时,调用该方法将节点移至队尾
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

// 该方法的过程就是对链表的操作,将本次访问的节点移动至tail后面生成新的tail
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

删除策略
LinkedHashMap添加节点调用的就是HashMap的put方法,该方法会调用HashMap中定义的putVal方法,putVal方法的最后有这样一行代码,
在这里插入图片描述
在HashMap中,afterNodeInsertion是空方法,没有代码。而LinkedHashMap对该方法进行了复写,

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 得到头节点,removeEldestEntry 控制删除策略,如果队列不为空,并且删除策略允许删除的情况下,返回true
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // HashMap中删除节点的方法
        removeNode(hash(key), key, null, false, true);
    }
}
  1. 获取链表头部节点
  2. 首先会通过removeEldestEntry方法判断是否满足删除节点的条件
  3. 如果满足,则调用父类的删除节点的removeNode方法对节点进行删除

总结

LinkedHashMap的两个特性,在不影响HashMap查找的情况下,做到按顺序访问和LRU删除,只是简单地对HashMap进行了拓展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值