一,简介
jdk中HashMap和HashSet的遍历是无序的,而LinkedHashMap和LinkedHashSet是两种可以预知迭代顺序的集合类。LinkedHashMap支持两种迭代顺序(插入顺序和访问顺序,其中默认是插入顺序)。而LinkedHashSet仅支持按插入顺序遍历。
二,实现原理
LinkedHashMap<K,V>继承自HashMap<K,V>,与HashMap的区别是LinkedHashMap底层还维护着一个双向循环链表(用于记录元素的插入顺序或访问顺序)。
结构图:
JDK源码:
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. */ private transient Entry<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 */ private final boolean accessOrder; public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false;//默认为插入顺序,如果要以访问顺序遍历,需要设置为true } public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } /** *LinkedHashMap重写了HashMap中的get方法 */ public V get(Object key) { Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; e.recordAccess(this);//记录访问顺序 return e.value; } /** * LinkedHashMap entry. * 由于继承于HashMap.Entry<K,V>,所以Entry<K,V>实际上包含以下三个Entry<K,V>属性:before,after,next(HashMap是基于单链表+数组实现的,next单链表节点的后继元素)。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after;//双向链表中节点的前驱和后继节点引用 Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } /** * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; } /** * Inserts this entry before the specified existing entry in the list. */ private void addBefore(Entry<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. */ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) {//如果是访问顺序,则修改header引用为最新访问的节点元素 lm.modCount++; remove(); addBefore(lm.header); } } void recordRemoval(HashMap<K,V> m) { remove(); } } }
LinkedHashMap中的访问顺序遍历,已经为我们实现LRU算法(最近最少使用算法)提供了便利。记录访问顺序:将最新访问的元素添加到双向循环链表的表头,并从原来的位置删除。由于链表的增加、删除操作是常量级的,故并不会带来性能的损失。LinkedHashMap类是重写了HashMap中的get方法(添加记录访问顺序逻辑)。
下面介绍一下LinkedHashSet的实现原理
LinkedHashSet是继承HashSet实现的,而HashSet是不具备按插入顺序遍历的。那么LinkedHashSet又是如何实现按插入顺序遍历的呢?JDK源码如下:
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -2851667679971038690L; /** * Constructs a new, empty linked hash set with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity of the linked hash set * @param loadFactor the load factor of the linked hash set * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive */ public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } /** * Constructs a new, empty linked hash set with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity of the LinkedHashSet * @throws IllegalArgumentException if the initial capacity is less * than zero */ public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } /** * Constructs a new, empty linked hash set with the default initial * capacity (16) and load factor (0.75). */ public LinkedHashSet() { super(16, .75f, true); } /** * Constructs a new linked hash set with the same elements as the * specified collection. The linked hash set is created with an initial * capacity sufficient to hold the elements in the specified collection * and the default load factor (0.75). * * @param c the collection whose elements are to be placed into * this set * @throws NullPointerException if the specified collection is null */ public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); } }
通过阅读JDK源码,发现LinkedHashSet中并没有像LinkedHashMap一样实现一个记录可预订顺序的双向循环链表。那肯定是HashSet帮LinkedHashSet做了这件事, 于是乎继续阅读HashSet实现源码。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<E,Object>(); } /**访问权限为public,组合的是HashMap*/ public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } /**访问权限为defalut为包内访问,组合的是LinkedHashMap,由于HashSet没有提供可以设置 * accessOrder属性的构造函数,则LinkedHashSet仅具备按插入顺序遍历的功能 */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); } }
通过阅读HashSet的源码,LinkedHashSet最终是组合LinkedHashMap来实现的,但由于父类HashSet没有提供赋值accessOrder属性的入口,所以只具备按插入顺序遍历的功能。平常我们在应用中使用的HashSet都是基于组合HashMap实现的,所以HashSet的遍历是无序的。
三,总结
LinkedHashMap(插入顺序和访问顺序)和LinkedHashSet(插入顺序),这两种集合类操作是非线程安全的。