LinkedHashMap类是构建在HashMap的基础上的。HashMap是数组+链表+红黑树的复合数据结构。LinkedHashMap在HashMap的基础上添加了head和tail指针。这两个指针会将HashMap中的元素链接起来,组成一个链表。下面我们通过图片来看下LinkedHashMap的结构。
上面的图片我们看到了LinkedHashMap的结构。下面我们将链表单独拎出来,其结构如下:
/** * The head (eldest) of the doubly linked list. */ transient LinkedHashMapEntry<K,V> head; /** * The tail (youngest) of the doubly linked list. */ transient LinkedHashMapEntry<K,V> tail; /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * * @serial */ final boolean accessOrder; 复制代码
上面的head和tail字段是表示链表的头结点和尾节点的。accessOrder字段可以定制LinkedHashMap的插入顺序。这个后面我们分析源码的时候详细介绍。
上面介绍了LinkedHashMap很多特性,主要都是其链表的相关特性(其大部分特性还是继承HashMap的)。我们来看下LinkedHashMap的节点结构:
/** * HashMap.Node subclass for normal LinkedHashMap entries. */ static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> { LinkedHashMapEntry<K,V> before, after; LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } 复制代码
节点结构很清晰,只是添加了before和after节点,分别指向前后的两个节点。
/** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the specified initial capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the specified initial capacity and a default load factor (0.75). * * @param initialCapacity the initial capacity * @throws IllegalArgumentException if the initial capacity is negative */ public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the default initial capacity (16) and load factor (0.75). */ public LinkedHashMap() { super(); accessOrder = false; } /** * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with * the same mappings as the specified map. The <tt>LinkedHashMap</tt> * instance is created with a default load factor (0.75) and an initial * capacity sufficient to hold the mappings in the specified map. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } /** * Constructs an empty <tt>LinkedHashMap</tt> instance with the * specified initial capacity, load factor and ordering mode. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @param accessOrder the ordering mode - <tt>true</tt> for * access-order, <tt>false</tt> for insertion-order * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } 复制代码
绝大多数方法都是使用的HashMap的,有些方法是经过LinkedHashMap复写的。下面我们来一一介绍这些方法。
我们介绍HashMap中的putVal的时候肯定看到如下代码:
afterNodeInsertion(evict); 复制代码
这个方法在HashMap中的实现是空的,LinkedHashMap复写了这个方法,我们来看源码:
void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMapEntry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } 复制代码
我们看到如果evict参数是true并且有头结点并且removeEldestEntry函数返回true的话就会删除头结点(这其实是LRU算法)。 细心的同学可能会问,上面不是说LinkedHashMap默认是实现是添加Node节点到链表尾部吗?没看见相关代码啊,只是看到了afterNodeInsertion方法。我们仔细的看源码会发现,如果需要插入节点的话肯定会调用newNode方法。我们来看HashMap中和LinkedHashMap中newNode的实现:
// Create a regular (non-tree) node Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next); } Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMapEntry<K,V> p = new LinkedHashMapEntry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } 复制代码
我们看到在HashMap中newNode只是返回一个Node节点对象。在LinkedHashMap中会先构造一个LinkedHashMapEntry对象,然后调用linkNodeLast将其链接到链表的尾部。源码如下:
// link at the end of list private void linkNodeLast(LinkedHashMapEntry<K,V> p) { LinkedHashMapEntry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } } 复制代码
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases.' */ 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; } 复制代码
主要还是会调用getNode方法,其次会根据accessOrder参数决定是否调用afterNodeAccess方法。我们来看下其相关逻辑:
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMapEntry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<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; } } 复制代码
从源码中我们看到,如果accessOrder为true就会将原来位置的节点e删除,然后将其链接到链表的尾部。accessOrder参数设置为true,LinkedHashMap就会使用符合LRU的特性,将最新访问的节点移动到链表的尾部(注意:这个移动只是在链表中,元素实际的在HashMap的存储还是不变的)。
上面我们或多或少已经介绍了使用LinkedHashMap来实现LRU的相关内容。这一节我们详细介绍下如何使用LInkedHashMap来定制自己的LRU算法。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } 复制代码
我们可以根据需要定制相关逻辑。下面我们来看下一个简单的例子
class LRUCache<T> extends LinkedHashMap { public LRUCache() { super(16, 0.75f, true); } @Override protected boolean removeEldestEntry(java.util.Map.Entry eldest) { return size()>3; } } 复制代码
在构造函数中我们设置accessOrder为true,让LinkedHashMap符合LRU的相关特性。同时我们复写removeEldestEntry方法。当Map中的元素超过3个时候将最早添加的元素移除。我们来些例子验证下:
LRUCache<String> lruCache=new LRUCache<>(); lruCache.put("1","A"); lruCache.put("2","B"); lruCache.put("3","C"); System.out.println(lruCache); //插入第4个元素(默认会移除第一个元素A) lruCache.put("4","D"); System.out.println(lruCache); //访问元素,会将C移动到链表的尾部 lruCache.get("3"); System.out.println(lruCache); //输出如下: {1=A, 2=B, 3=C} {2=B, 3=C, 4=D} {2=B, 4=D, 3=C} 复制代码