`

LinkedHashMap与LinkedHashSet可预知迭代顺序

    博客分类:
  • java
阅读更多

一,简介

       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(插入顺序),这两种集合类操作是非线程安全的。


 
 

  • 大小: 26.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics