LinkedHashMap源码分析及高频面试问题答案

首先看继承结构

public class LinkedHashMap<K,V>  extends HashMap<K,V>  implements Map<K,V>

可以看到LinkedHashMap继承自 HashMap.

成员变量

transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;

有头节点,尾节点,及是否按访问顺序的布尔值。
该accessOrder被final修饰,即不可改变。
需要注意的是,Entry即节点并是从HashMap中Node继承过来的。我们看下这个Entry

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);
    }
}

可以看到该节点相比父类增加了before,after两个节点。

构造方法

public LinkedHashMap() {
    super();
    accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
  	super(initialCapacity, loadFactor);
    accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
     super();
     accessOrder = false;
     putMapEntries(m, false);
 }
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
   super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

几种构造方法跟hashmap基本一致,无非是如果不传入了accessOrder,则默认置此常量为false。

几个主要方法

增:LinkedHashMap并没有重写put方法,只是实现了hashmap中几个方法, 如:afterNodeInsertion(),fterNodeAccess()等

void afterNodeInsertion(boolean evict) { // possibly remove eldest
  	 LinkedHashMap.Entry<K,V> first;
     if (evict && (first = head) != null && removeEldestEntry(first)) {
         K key = first.key;
         removeNode(hash(key), key, null, false, true);
     }
 }
 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
    

evict 在hashmap默认是true。removeEldestEntry总返回false,所以一直此函数什么都没做。

那么插入是有序必然是在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;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

果然,可以看到该方法中维持一个双向链表,将最新加入的节点,放在该链表末尾。那么取数据只需要按照此链表遍历即可,就实现了按添加顺序为查询顺序的有序链表。

看下遍历方法

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

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            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 = 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 = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

如我们所想,遍历该双向链表。

如果是按访问顺序呢,是怎么实现的?

我们看下get方法

public V get(Object key) {
  Node<K,V> e;
     if ((e = getNode(hash(key), key)) == null)
         return null;
     if (accessOrder)
         afterNodeAccess(e);
     return e.value;
 }
 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;
     }
 }

可以看到是将该节点放到了双向链表的结尾。

面试中经常问到的相关问题:

1.说一下LinkedHashMap的数据结构

LinkedHashMap继承自HashMap,并维持了一个双向链表。插入节点时,将节点追加到双向链表尾部,从而实现按照插入顺序的有序访问。也可以在初始化LinkedHashMap对象时设定为按照访问顺序排序,此时每当访问一个节点,afternodeaccess方法就会将该节点放到双向链表的尾部,从而实现按照访问顺序的有序遍历访问。

2.如何利用LinkedHashMap实现一个Cache

由于accessOrder为true的LinkedHashMap已实现了最新访问的节点放到双向链表的末尾,所以很方便通过删除链表头部节点实现缓存的lru算法。
具体来说,只需要继承LinkedHashMap类,并重写removeEldestEntry方法,将该方法改为超出一定长度返回true 即可。

final int cacheNum;
public LinkedHashMap(int cacheNum) {
        super();
        cacheNum=cacheNum;
        accessOrder = false;
    }
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
	if(size >= cacheNum){
		return true;
	}
        return false;
    }

以上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值