LinkedHashMap源码分析
来源:互联网 发布:php代码美化 编辑:程序博客网 时间:2024/06/07 00:44
参考:【Java集合源码剖析】LinkedHashmap源码剖析
entry: [ˈentrɪ] 进入;入口
LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同。
LinkedHashMap可以用来实现LRU算法。
LinkedHashMap同样是非线程安全的,只在单线程环境下使用。
package java.util;import sun.misc.Hashing;import java.io.*;import java.util.function.BiFunction;import java.util.function.Consumer;import java.util.function.BiConsumer;public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{ private static final long serialVersionUID = 3801124242820219131L; /** * The head of the doubly linked list. */ //双向循环链表的头结点,整个LinkedHashMap中只有一个header, //它将哈希表中所有的Entry贯穿起来,header中不保存key-value对,只保存前后节点的引用 private transient LinkedHashMapEntry<K,V> header; /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * * @serial */ //双向链表中元素排序规则的标志位。 //accessOrder为false,表示按插入顺序排序 //accessOrder为true,表示按访问顺序排序 private final boolean accessOrder; //---------------------------------构造方法开始-------------------------------------- /** * 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 */ //调用HashMap的构造方法来构造底层的数组 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 */ //加载因子取默认的0.75f 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). */ //加载因子取默认的0.75f,容量取默认的16 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 */ //含有子Map的构造方法,同样调用HashMap的对应的构造方法 public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = 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; } //---------------------------------构造方法结束-------------------------------------- /** * Called by superclass constructors and pseudoconstructors (clone, * readObject) before any entries are inserted into the map. Initializes * the chain. */ //覆写父类的init()方法(HashMap中的init方法为空), //该方法在父类的构造方法和Clone、readObject中在插入元素前被调用, //初始化一个空的双向循环链表,头结点中不保存数据,头结点的下一个节点才开始保存数据。 @Override void init() { header = new LinkedHashMapEntry<>(-1, null, null, null); header.before = header.after = header; } /** * Transfers all entries to new table array. This method is called * by superclass resize. It is overridden for performance, as it is * faster to iterate using our linked list. */ //覆写HashMap中的transfer方法,它在父类的resize方法中被调用, //扩容后,将key-value对重新映射到新的newTable中 //覆写该方法的目的是为了提高复制的效率, //这里充分利用双向循环链表的特点进行迭代,不用对底层的数组进行for循环。 @Override void transfer(HashMapEntry[] newTable) { int newCapacity = newTable.length; for (LinkedHashMapEntry<K,V> e = header.after; e != header; e = e.after) { int index = indexFor(e.hash, newCapacity); e.next = newTable[index]; newTable[index] = e; } } /** * Returns <tt>true</tt> if this map maps one or more keys to the * specified value. * * @param value value whose presence in this map is to be tested * @return <tt>true</tt> if this map maps one or more keys to the * specified value */ //覆写HashMap中的containsValue方法, //覆写该方法的目的同样是为了提高查询的效率, //利用双向循环链表的特点进行查询,少了对数组的外层for循环 public boolean containsValue(Object value) { // Overridden to take advantage of faster iterator if (value==null) { for (LinkedHashMapEntry e = header.after; e != header; e = e.after) if (e.value==null) return true; } else { for (LinkedHashMapEntry e = header.after; e != header; e = e.after) if (value.equals(e.value)) return true; } return false; } /** * 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. */ //覆写HashMap中的get方法,通过getEntry方法获取Entry对象。 //注意这里的recordAccess方法, //如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做, //如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。 public V get(Object key) { LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key); if (e == null) return null; e.recordAccess(this);//取得Entry,如果不为null,调用recordAccess方法 return e.value; } /** * Removes all of the mappings from this map. * The map will be empty after this call returns. */ //清空HashMap,并将双向链表还原为只有头结点的空链表 public void clear() { super.clear(); header.before = header.after = header; } /** * LinkedHashMap entry. */ //Enty的数据结构,多了两个指向前后节点的引用 private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> { // These fields comprise the doubly linked list used for iteration. LinkedHashMapEntry<K,V> before, after; //调用父类的构造方法 LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) { super(hash, key, value, next); } /** * Removes this entry from the linked list. */ //双向循环链表中,删除当前的Entry private void remove() { before.after = after; after.before = before; } /** * Inserts this entry before the specified existing entry in the list. */ //双向循环立链表中,将当前的Entry插入到existingEntry的前面 private void addBefore(LinkedHashMapEntry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } /** * This method is invoked by the superclass whenever the value * of a pre-existing entry is read by Map.get or modified by Map.set. * If the enclosing Map is access-ordered, it moves the entry * to the end of the list; otherwise, it does nothing. */ //覆写HashMap中的recordAccess方法(HashMap中该方法为空), //当调用父类的put方法,在发现插入的key已经存在时,会调用该方法, //调用LinkedHashmap覆写的get方法时,也会调用到该方法, //该方法提供了LRU算法的实现,它将最近使用的Entry放到双向循环链表的尾部, //accessOrder为true时,get方法会调用recordAccess方法 //put方法在覆盖key-value对时也会调用recordAccess方法 //它们导致Entry最近使用,因此将其移到双向链表的末尾 void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; //如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部, //如果是按照插入的先后顺序排序,则不做任何事情。 if (lm.accessOrder) { lm.modCount++; remove(); //移除当前访问的Entry addBefore(lm.header);//将当前访问的Entry插入到链表的尾部 } } void recordRemoval(HashMap<K,V> m) { remove(); } } //迭代器 private abstract class LinkedHashIterator<T> implements Iterator<T> { LinkedHashMapEntry<K,V> nextEntry = header.after; LinkedHashMapEntry<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; } //从head的下一个节点开始迭代 Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (nextEntry == header) throw new NoSuchElementException(); LinkedHashMapEntry<K,V> e = lastReturned = nextEntry; nextEntry = e.after; return e; } } //key迭代器 private class KeyIterator extends LinkedHashIterator<K> { public K next() { return nextEntry().getKey(); } } //value迭代器 private class ValueIterator extends LinkedHashIterator<V> { public V next() { return nextEntry().getValue(); } } //Entry迭代器 private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } // These Overrides alter the behavior of superclass view iterator() methods Iterator<K> newKeyIterator() { return new KeyIterator(); } Iterator<V> newValueIterator() { return new ValueIterator(); } Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); } /** * This override alters behavior of superclass put method. It causes newly * allocated entry to get inserted at the end of the linked list and * removes the eldest entry if appropriate. */ void addEntry(int hash, K key, V value, int bucketIndex) { // Previous Android releases called removeEldestEntry() before actually // inserting a value but after increasing the size. // The RI is documented to call it afterwards. // **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE **** // Remove eldest entry if instructed LinkedHashMapEntry<K,V> eldest = header.after; if (eldest != header) { boolean removeEldest; size++; try { removeEldest = removeEldestEntry(eldest); } finally { size--; } if (removeEldest) { removeEntryForKey(eldest.key); } } super.addEntry(hash, key, value, bucketIndex); } /** * Returns the eldest entry in the map, or {@code null} if the map is empty. * * Android-added. * * @hide */ //提供最近最少使用的元素: public Map.Entry<K, V> eldest() { Entry<K, V> eldest = header.after; return eldest != header ? eldest : null; } /** * This override differs from addEntry in that it doesn't resize the * table or remove the eldest entry. */ void createEntry(int hash, K key, V value, int bucketIndex) { HashMapEntry<K,V> old = table[bucketIndex]; LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; } // Intentionally make this not JavaDoc, as the we don't conform to // the behaviour documented here (we call removeEldestEntry before // inserting the new value to be consistent with previous Android // releases). // **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE **** /* * Returns <tt>true</tt> if this map should remove its eldest entry. * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after * inserting a new entry into the map. It provides the implementor * with the opportunity to remove the eldest entry each time a new one * is added. This is useful if the map represents a cache: it allows * the map to reduce memory consumption by deleting stale entries. * * <p>Sample use: this override will allow the map to grow up to 100 * entries and then delete the eldest entry each time a new entry is * added, maintaining a steady state of 100 entries. * <pre> * private static final int MAX_ENTRIES = 100; * * protected boolean removeEldestEntry(Map.Entry eldest) { * return size() > MAX_ENTRIES; * } * </pre> * * <p>This method typically does not modify the map in any way, * instead allowing the map to modify itself as directed by its * return value. It <i>is</i> permitted for this method to modify * the map directly, but if it does so, it <i>must</i> return * <tt>false</tt> (indicating that the map should not attempt any * further modification). The effects of returning <tt>true</tt> * after modifying the map from within this method are unspecified. * * <p>This implementation merely returns <tt>false</tt> (so that this * map acts like a normal map - the eldest element is never removed). * * @param eldest The least recently inserted entry in the map, or if * this is an access-ordered map, the least recently accessed * entry. This is the entry that will be removed it this * method returns <tt>true</tt>. If the map was empty prior * to the <tt>put</tt> or <tt>putAll</tt> invocation resulting * in this invocation, this will be the entry that was just * inserted; in other words, if the map contains a single * entry, the eldest entry is also the newest. * @return <tt>true</tt> if the eldest entry should be removed * from the map; <tt>false</tt> if it should be retained. */ //该方法是用来被覆写的,一般如果用LinkedHashmap实现LRU算法,就要覆写该方法, //比如可以将该方法覆写为如果设定的内存已满,则返回true,这样当再次向LinkedHashMap中put //Entry时,在调用的addEntry方法中便会将近期最少使用的节点删除掉(header后的那个节点)。 //该方法默认返回false,我们一般在用LinkedHashMap实现LRU算法时,要覆写该方法, //一般的实现是,当设定的内存(这里指节点个数)达到最大值时,返回true, //这样put新的Entry(该Entry的key在哈希表中没有已经存在)时,就会调用removeEntryForKey方法, //将最近最少使用的节点删除(head后面的那个节点,实际上是最近没有使用)。 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } // Map overrides public void forEach(BiConsumer<? super K, ? super V> action) { if (action == null) throw new NullPointerException(); int mc = modCount; // Android modified - breaks from the loop when modCount != mc for (LinkedHashMapEntry<K,V> e = header.after; modCount == mc && e != header; e = e.after) action.accept(e.key, e.value); if (modCount != mc) throw new ConcurrentModificationException(); } public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { if (function == null) throw new NullPointerException(); int mc = modCount; // Android modified - breaks from the loop when modCount != mc for (LinkedHashMapEntry<K,V> e = header.after; modCount == mc && e != header; e = e.after) e.value = function.apply(e.key, e.value); if (modCount != mc) throw new ConcurrentModificationException(); }}
1、LinkedHashMap是如何实现LRU的。
首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。
我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。
2、LinkedHashMap由于继承自HashMap,因此它具有HashMap的所有特性,同样允许key和value为null。
3、注意源码中的accessOrder标志位,
当它false时,表示双向链表中的元素按照Entry插入LinkedHashMap到中的先后顺序排序,即每次put到LinkedHashMap中的Entry都放在双向链表的尾部,这样遍历双向链表时,Entry的输出顺序便和插入的顺序一致,这也是默认的双向链表的存储顺序;
当它为true时,表示双向链表中的元素按照访问的先后顺序排列,可以看到,虽然Entry插入链表的顺序依然是按照其put到LinkedHashMap中的顺序,但put和get方法均有调用recordAccess方法(put方法在key相同,覆盖原有的Entry的情况下调用recordAccess方法),该方法判断accessOrder是否为true,如果是,则将当前访问的Entry(put进来的Entry或get出来的Entry)移到双向链表的尾部(key不相同时,put新Entry时,会调用addEntry,它会调用creatEntry,该方法同样将新插入的元素放入到双向链表的尾部,既符合插入的先后顺序,又符合访问的先后顺序,因为这时该Entry也被访问了),否则,什么也不做。
4、注意构造方法,前四个构造方法都将accessOrder设为false,说明默认是按照插入顺序排序的,而第五个构造方法可以自定义传入的accessOrder的值,因此可以指定双向循环链表中元素的排序规则,一般要用LinkedHashMap实现LRU算法,就要用该构造方法,将accessOrder置为true。
5、LinkedHashMap并没有覆写HashMap中的put方法,而是覆写了put方法中调用的addEntry方法和recordAccess方法。
- HashMap LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- 《Java源码分析》:LinkedHashMap
- LinkedHashMap及其源码分析
- LinkedHashMap源码分析
- LinkedHashMap及其源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap及其源码分析
- 《Java源码分析》:LinkedHashMap
- LinkedHashMap源码分析
- LinkedHashMap源码分析
- LinkedHashMap的源码分析
- spring懒加载以及@PostConstruct结合的坑
- win2008r2 ftp图解设置-第二篇
- maven不能下载oracle jdbc驱动的解决方法
- 关于图片大小的理解
- 蓝牙核心技术概述(五):蓝牙协议规范(irOBEX、BNEP、AVDTP、AVCTP)
- LinkedHashMap源码分析
- 树莓派学习(一)
- Docker学习笔记(1)
- RGB value of various colors
- vsftp给用户指定访问目录,而且不能访问上层目录
- 500. Keyboard Row 难度:简单
- 对象关系映射
- 数据结构 第5讲 顺序栈
- Unexpected 'a' 错误