package java.util; import java.util.function.Consumer; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.io.IOException; import HashMap.Node; /** * 哈希表和链表实现Map接口,并具有固定迭代顺序。 * LinkedHashMap实现和HashMap的不同之处在于LinkedHashMap维护了一个双向链表来存储所有的Entry。 * 这个链表定义了迭代顺序,即关键字插入Map的顺序。重复插入关键字不会影响顺序。 * Hashtable和HashMap提供无序的键值对。 * 有一个特殊的构造函数LinkedHashMap(int,float,boolean) constructor用于创建一个链式Hash Map,它的迭代顺序是Entry上次访问顺序, * 从最近访问最少到最近访问最多。非常适合建立LRU caches。 * 集合视图上的操作不会影响Map的遍历。 * removeEldestEntry(Map.Entry)允许重写自定义规则,在新映射添加的时候自动移除一些映射。 * 该类实现了Map的所有可选操作,允许空null值。 * HashMap提供常数时间的基本操作(add, contains, remove),假设哈希函数将元素均匀的散列在各槽中。 * 由于需要维护链表,LinkedHashMap的性能略低于HashMap。 * 但有一个例外:在LinkedHashMap的集合视图中遍历的时间和Size成正比,和capacity无关, * 而HashMap的集合视图中遍历的时间和capacity成正比。 * * LinkedHashMap有两个参数影响其性能:初始容量initial capacity,装载因子load factor,HashMap也是如此。 * * 该实现也是非线程安全的。多线程访问时,需要外部同步。例如,需要Collections#synchronizedMap Collections.synchronizedMap方法包装: * Map m = Collections.synchronizedMap(new LinkedHashMap(...)); * 在以访问决定顺序的链式HashMap中只有get是结构上的操作。 * LinkedHashMap的集合视图也都是快速失败的。 * * 底层使用哈希表与双向链表来保存所有元素,其基本操作与父类HashMap相似,它通过重写父类相关的方法来实现自己的链表特性。 * 即LinkedHashMap的结点有:final int hash; //不可变的哈希值————由关键字key得来 final K key; //关键字不可变 V value; Node<K,V> next; Entry<K,V> before; Entry<K,V> after; */ public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { /* * LinkedHashMap的结点Entry也可以转换成树的形式。 * 结点类中使用(head,tail)来维护一个双向链表(before,after)。 * 回调函数的风格也不太一样。 */ /** * LinkedHashMap的Entry类型继承自HashMap.Node * 该Entry除了保存当前对象的引用外,还保存了其上一个元素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); } } private static final long serialVersionUID = 3801124242820219131L; /** * 双向链表的头结点(最早的结点). */ transient LinkedHashMap.Entry<K,V> head; /** * 双向链表的尾结点(最晚的结点). */ transient LinkedHashMap.Entry<K,V> tail; /** * LinkedHashMap的迭代顺序:true是访问顺序(即最近最少使用次序),false是插入顺序 */ final boolean accessOrder; // 内部工具 // 链到链表末端 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; } } // apply src's links to dst /** * 将src的链接链到dst上————用dst替换src */ private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) { LinkedHashMap.Entry<K,V> b = dst.before = src.before; LinkedHashMap.Entry<K,V> a = dst.after = src.after; //分别考虑首尾情况 //src是头结点 if (b == null) head = dst; else b.after = dst; //src是尾结点 if (a == null) tail = dst; else a.before = dst; } // 重写HashMap的方法 void reinitialize() { super.reinitialize(); //重新初始化head,tail,均为null head = tail = null; } //创建新节点并链接到尾部 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); //将p链到尾部 linkNodeLast(p); return p; } //替换结点,用next替换p Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; LinkedHashMap.Entry<K,V> t = new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next); //用t替换q transferLinks(q, t); return t; } TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) { TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next); linkNodeLast(p); return p; } TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next); transferLinks(q, t); return t; } //删除结点e后调整 void afterNodeRemoval(Node<K,V> e) { // 解链 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.before = p.after = null; //分别考虑首尾情况 if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; } void afterNodeInsertion(boolean evict) { // 可能需要移除最老结点 LinkedHashMap.Entry<K,V> first; //removeEldestEntry(first)始终返回false,所以这里移除结点后不会有操作,不会移除最老结点 //这样做的目的是方便开发者可以把Map当cache来用,并且可以限制大小,只需继承LinkedHashMap并重写removeEldestEntry() if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } /** * 当accessOrder为true(按最近最少使用)时,结点访问(get,put操作)后需要调整结点顺序, * 将当前被操作节点移动到head结点之前,即链表的尾部 */ void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; //先将结点e从原链表中解除出来,再链到head之前 if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //需要分别考虑p结点是head和tail的情况 //断开p=e与其后继结点的关联 p.after = null; //若p=e的前一个结点为空,则p为原头结点head,所以删除后,p的原后继结点a成为新的头结点head if (b == null) head = a; //p不是头结点,则b的后继指向a结点 else b.after = a; //后继结点a不为空,a指向p的前驱结点b if (a != null) a.before = b; //后继结点为空,则p结点为尾结点tail,所以删除后,p的前驱成为新的尾结点 else last = b; //若last=b为空,则p为头结点,现在 if (last == null) head = p; //last不为空 else { p.before = last; last.after = p; } //将p结点链到尾部 tail = p; ++modCount; } } void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) { s.writeObject(e.key); s.writeObject(e.value); } } /** * 构造方法1:购造一个指定初始容量和负载因子的、按照插入顺序的LinkedHashMap * @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) { //调用父类HashMap的相关构造方法来构造一个底层存放的table数组, //但额外可以增加accessOrder这个参数,若不设置,默认false,代表按插入顺序迭代 //true为按访问顺序进行迭代 //HashMap的构造器中会调用init()方法,进行相关的初始化,这个方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用 super(initialCapacity, loadFactor); accessOrder = false; } /** * 构造方法2:构造一个指定初始容量的LinkedHashMap,取得键值对的顺序是插入顺序,默认装载因子0.75 * @param initialCapacity the initial capacity * @throws IllegalArgumentException if the initial capacity is negative */ public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } /** * 构造方法3:用默认的初始化容量16和负载因子0.75创建一个LinkedHashMap,取得键值对的顺序是插入顺序 */ public LinkedHashMap() { super(); accessOrder = false; } /** * 构造方法4:通过传入的Map创建一个LinkedHashMap,容量为默认容量(16)和(map.size()/DEFAULT_LOAD_FACTORY)+1的较大者,、 * 装载因子为默认值0.75 * @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); } /** * 构造方法5:根据指定容量、装载因子和键值对保持顺序创建一个LinkedHashMap * @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; } /** * 若有一个以上关键字映射到该值,返回true * 重写父类的containsValue(Object value)方法,直接通个关header遍历链表判断是否有值和value相等 * 不用查询table数组 * @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 */ public boolean containsValue(Object value) { //通过header遍历链表判断是否有值和value相等 for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) { V v = e.value; if (v == value || (value != null && value.equals(v))) return true; } return false; } /** * 返回指定关键字映射到的值,不存在则返回null */ public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; //如果按照访问顺序(accessOrder为true),需要调整结点顺序 if (accessOrder) afterNodeAccess(e); return e.value; } /** * {@inheritDoc} */ public V getOrDefault(Object key, V defaultValue) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return defaultValue; if (accessOrder) afterNodeAccess(e); return e.value; } /** * 清空LinkedHashMap */ public void clear() { //调用父类的方法clear() super.clear(); //将链表的header结点的before和after引用都指向null //这样就无法访问到原链表中剩余的其他结点,他们都将被GC回收 head = tail = null; } /** * 如果Map需要移除最老结点,返回true * 这样做的目的是方便开发者可以把Map当cache来用,并且可以限制大小,只需继承LinkedHashMap并重写removeEldestEntry() * put和putAll方法之后需要调用该函数 * <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. */ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } /** * Returns a {@link Set} view of the keys contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through * the iterator's own <tt>remove</tt> operation), the results of * the iteration are undefined. The set supports element removal, * which removes the corresponding mapping from the map, via the * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> * operations. It does not support the <tt>add</tt> or <tt>addAll</tt> * operations. * Its {@link Spliterator} typically provides faster sequential * performance but much poorer parallel performance than that of * {@code HashMap}. * * @return a set view of the keys contained in this map */ public Set<K> keySet() { Set<K> ks; return (ks = keySet) == null ? (keySet = new LinkedKeySet()) : ks; } final class LinkedKeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { LinkedHashMap.this.clear(); } public final Iterator<K> iterator() { return new LinkedKeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return Spliterators.spliterator(this, Spliterator.SIZED | Spliterator.ORDERED | Spliterator.DISTINCT); } public final void forEach(Consumer<? super K> action) { if (action == null) throw new NullPointerException(); int mc = modCount; for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) action.accept(e.key); if (modCount != mc) throw new ConcurrentModificationException(); } } /** * Returns a {@link Collection} view of the values contained in this map. * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. If the map is * modified while an iteration over the collection is in progress * (except through the iterator's own <tt>remove</tt> operation), * the results of the iteration are undefined. The collection * supports element removal, which removes the corresponding * mapping from the map, via the <tt>Iterator.remove</tt>, * <tt>Collection.remove</tt>, <tt>removeAll</tt>, * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not * support the <tt>add</tt> or <tt>addAll</tt> operations. * Its {@link Spliterator} typically provides faster sequential * performance but much poorer parallel performance than that of * {@code HashMap}. * * @return a view of the values contained in this map */ public Collection<V> values() { Collection<V> vs; return (vs = values) == null ? (values = new LinkedValues()) : vs; } final class LinkedValues extends AbstractCollection<V> { public final int size() { return size; } public final void clear() { LinkedHashMap.this.clear(); } public final Iterator<V> iterator() { return new LinkedValueIterator(); } public final boolean contains(Object o) { return containsValue(o); } public final Spliterator<V> spliterator() { return Spliterators.spliterator(this, Spliterator.SIZED | Spliterator.ORDERED); } public final void forEach(Consumer<? super V> action) { if (action == null) throw new NullPointerException(); int mc = modCount; for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) action.accept(e.value); if (modCount != mc) throw new ConcurrentModificationException(); } } /** * Returns a {@link Set} view of the mappings contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through * the iterator's own <tt>remove</tt> operation, or through the * <tt>setValue</tt> operation on a map entry returned by the * iterator) the results of the iteration are undefined. The set * supports element removal, which removes the corresponding * mapping from the map, via the <tt>Iterator.remove</tt>, * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and * <tt>clear</tt> operations. It does not support the * <tt>add</tt> or <tt>addAll</tt> operations. * Its {@link Spliterator} typically provides faster sequential * performance but much poorer parallel performance than that of * {@code HashMap}. * * @return a set view of the mappings contained in this map */ 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() { return new LinkedEntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { return Spliterators.spliterator(this, Spliterator.SIZED | Spliterator.ORDERED | Spliterator.DISTINCT); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { if (action == null) throw new NullPointerException(); int mc = modCount; for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) action.accept(e); if (modCount != mc) throw new ConcurrentModificationException(); } } // Map overrides public void forEach(BiConsumer<? super K, ? super V> action) { if (action == null) throw new NullPointerException(); int mc = modCount; for (LinkedHashMap.Entry<K,V> e = head; e != null; 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; for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) e.value = function.apply(e.key, e.value); if (modCount != mc) throw new ConcurrentModificationException(); } // Iterators 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; } } 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(); } } }