java-LinkedHashMap和LinkedHashSet源码分析

上一篇文章中,详细说明了HashMap和HashSet的源码,从源码的角度可以看出两者存在深入的联系,推测而来,LinkedHashMap和LinkedHashSet必然也存在着深入的联系。经过一下分析你会发现,两者的联系和HashMap和HashSet的联系一样。

废话不多说,首先LinkedHashMap源码:

  • LinkedHashMap源码
/* * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * * @author Josh Bloch * @see Object#hashCode() * @see Collection * @see Map * @see HashMap * @see TreeMap * @see Hashtable * @since 1.4 * 继承自HashMap * 主要作用是:能够保存存放元素的先后顺序 * 当进行元素的遍历的时候, * 能够按照存放的先后顺序进行遍历。 * 因此性能弱于HashMap的性能。 * 但在遍历所有元素时具有较好的性能。 */
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;

    /** * 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(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. * 初始化一个头结点 * 头结点的前向节点和后向节点都指向头结点自己 */
    @Override
    void init() {
        header = new Entry<>(-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. * 转移所有的entries元素到新的table数组中。 * 此处的transfer要比HashMap里面的transfer简单多了。 * 主要是因为有何header.双向链表。通过header就可以遍历原来数组 * 中的所有元素。 * 要做的公共就是把原来的数组中的元素拷贝到新数组中去就可以了。 * HashMap中transfer之所以看着麻烦是因为用了两层循环, * 第二层循环它需要保存链表中的位置。有三句赋值语句,让人觉得绕。 */
    @Override
    void transfer(HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
            if (rehash)
                e.hash = (e.key == null) ? 0 : hash(e.key);
            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 * 是否包含value值。简单,一看就明白。 */
    public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        if (value==null) {
            for (Entry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (Entry 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. * get方法,通过key获取值。 * getEntry(key)使用父类中的方法,有key值得到hash值, * 然后得到数组索引index,遍历链表得到Entry值。 */
    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;
    }

    /** * Removes all of the mappings from this map. * The map will be empty after this call returns. * 清除所有元素 * 前向引用、后向引用均指向自己 */
    public void clear() {
        super.clear();
        header.before = header.after = header;
    }

    /** * LinkedHashMap entry. * 集成父类的内部类 */
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        /** * 前一个节点和后一个节点,用于记录元素添加的顺序,用于元素遍历 * 与next的区别在于,next是解决冲突的问题而可能形成的链表 */
        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. * 删除当前元素。也就是调用该方法的this * 把前一个元素的后向引用自己的后向 * 把后一个元素的前向引用自己的前向 */
        private void remove() {
            before.after = after;
            after.before = before;
        }

        /** * Inserts this entry before the specified existing entry in the list. * 在参数existingEntry的前面插入元素 * 也就是将调用该方法的this元素插入existingEntry的前面。 * this.after = existingEntry; * this.before = existingEntry.before; * this.before.after = this; * this.after.before = this; * 以上四步赋值操作全部都对this操作。很容易明白。 */
        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) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }
    /** * 迭代器 * 此时的迭代器按照插入顺序进行迭代 */
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
        Entry<K,V> nextEntry    = header.after;
        Entry<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;
        }
        /** * 删除上一次遍历的元素 * lastReturned代表上一次遍历的数据 * lastReturned在nextEntry()方法中会保存上一次遍历的数据 */
        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }
        /** * 1、迭代器的关键是nextEntry()方法 * 2、迭代的过程中,不允许修改集合中的数据。 * 只能使用remove()方法删除上一个遍历的数据。 * 否则modCount与expectedModCount不等,将引发异常。 * 3、遍历过程中,会使用hasNext()方法判断是否还有下一个元素。 * 所以正常情况下,不会引发NoSuchElementException异常。 * 4、nextEntry代表下一个元素。 * e代表当前要遍历的元素。 * lastReturned代表上一次遍历的元素, * 用于remove方法中删除上一次遍历的元素。 * 5、LinkedHashMap能够按照插入顺序进行遍历的原因, * 就在于使用了before和after,分别保存了前向和后向元素 * */
        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
        }
    }

    private class KeyIterator extends LinkedHashIterator<K> {
        public K next() { return nextEntry().getKey(); }
    }

    private class ValueIterator extends LinkedHashIterator<V> {
        public V next() { return nextEntry().value; }
    }

    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. * 这里调用父类的addEntry方法。 * 父类的addEntry方法会resize判断,然后调用createEntry方法。 * 说白了这里调用父类的方法进行了resize判断,然后使用 * 自己的createEntry方法把数据添加进来。 */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            /** * 这个方法是通过key删除entry元素。 * 由于if语句中返回false,下面语句不会执行, * 所以removeEntryForKey方法并没有重写。 * 如果要执行删除操作,LinkedHashMap类必须重写 * 该方法。因为父类的删除操作并没有改变本类中 * 前向和后向引用的指向。 */
            removeEntryForKey(eldest.key);
        }
    }

    /** * This override differs from addEntry in that it doesn't resize the * table or remove the eldest entry. * 添加新元素 * 得到table数组中的原来bucketIndex位置的数据, * 创建一个新的Entry,并与原来bucketIndex位置的数据形成链表, * 然后保存在table数组中。 * 这几步与HashMap中的createEntry()方法一致。 * e.addBefore(header);实现把header添加到e的前面。 * */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
}

源码中的英文注释部分并没有去掉,方便大家查看对照。

对于LinkedHashMap要说的,也没什么特别的地方,理解了HashMap的话,基本理解LinkedHashMap也无压力了。

1、首先明确LinkedHashMap的功能,就是在HashMap的基础上实现遍历元素的时候按照插入顺序进行遍历。为实现这个功能,就在Entry类中定义了前向引用和后向引用。其实完全可以仅仅使用一个后向引用就可以实现的。那么为什么采用双向链表呢?
个人理解的原因是:通过使用双向链表,链表在元素的插入、删除等操作时易于维护。如果仅仅有一个后向引用,删除、插入元素时都要通过header引用遍历到待删除的元素的前一个元素,那么将会是很麻烦的事情。必然降低性能。所以通过双向链表,插入删除都很方便。
2、双向链表头引用。header是LinkedHashMap中维护的一个Entry变量,该引用代表第一个插入的元素。遍历的时候,会从该元素开始进行遍历。
3、一个好图,不得不盗过来哈哈~~

很说明问题的一张图。
4、当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {  
       return false;  
   } 

返回false表示不过期,不过此方法可以通过继承该类进行重写,这样可以实现定期删除老旧数据的目的。
此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。

  • LinkedHashSet源码
/*
 * @author  Josh Bloch
 * @see     Object#hashCode()
 * @see     Collection
 * @see     Set
 * @see     HashSet
 * @see     TreeSet
 * @see     Hashtable
 * @since   1.4
 * 该类同样实现的功能是:
 * 遍历全部元素时可以按照插入顺序遍历。
 * 使得遍历得到的数据顺序和插入顺序一致。
 * 该类非常简单,就是四个构造器。
 * 全部调用父类的同一个构造器完成。
 *  HashSet(int initialCapacity, float loadFactor, boolean dummy) {
 *       map = new LinkedHashMap<>(initialCapacity, loadFactor);
 *   }
 * 这就是所调用的父类的构造器。
 * 第三个参数dummy是忽略,并无用处。
 * 从构造器中可以看出,里面使用了LinkedHashMap来完成LinkedHashSet
 * 所要实现的功能。
 */

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

代码中注释部分已经说得很明白了,该类非常简单,只要看明白LinkedHashMap即可。该类只有四个构造器没有方法。全部复用父类的方法实现。

如果大家有什么问题或疑问,欢迎批评真正!本人水平有限,多多指教!!【握手~】^_^

你可能感兴趣的:(源码,HashMap,hashset,LinkedHash)