HashSet源码解析&Map迭代器

1 HashSet源码解析

HashSet也是一种java容器,这里不再赘述hash的概念原理等一大堆东西了,需要在啰嗦一句的是hash表是基于快速存取的角度设计的,也是一种典型的空间换时间的做法
先来看下Set的特点:Set元素无顺序,且元素不可以重复
无顺序,由于散列的缘故;不可重复,HashMapkey就是不能重复的。HashSet就是基于HashMapkey来实现的,整个HashSet中基本所有方法都是调用的HashMap的方法。利用HashMap可以实现两点:1.不可重复,2.快速查找。

1.1 定义

 public class HashSet<E>
     extends AbstractSet<E>
     implements Set<E>, Cloneable, java.io.Serializable

HashSet继承了AbstractSet抽象类,并实现了Set、Cloneable、Serializable接口。AbstractSet是一个抽象类,对一些基础的set操作进行封装。继续来看下Set接口的定义:

  public interface Set<E> extends Collection<E> {
      // Query Operations 
           int size();
      boolean isEmpty();
      boolean contains(Object o);
      Iterator<E> iterator();
      Object[] toArray();
      <T> T[] toArray(T[] a);
      // Modification Operations
           boolean add(E e);
     boolean remove(Object o);
     // Bulk Operations
          boolean containsAll(Collection<?> c);
     boolean addAll(Collection<? extends E> c);
     boolean retainAll(Collection<?> c);
     boolean removeAll(Collection<?> c);
     void clear();
     // Comparison and hashing
          boolean equals(Object o);
     int hashCode();
     @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.DISTINCT);
    }
 }

Set接口和java.util.List接口一样也实现了Collection接口,但是SetList所不同的是,Set没有get等跟下标相关的一些操作方法,那怎么取值呢?使用迭代器Iterator

1.2 底层存储

     // 底层使用HashMap来保存HashSet的元素
     private transient HashMap<E,Object> map;
     // 由于Set只使用到了HashMap的key,所以此处定义一个静态的常量Object类,来充当HashMap的value
     private static final Object PRESENT = new Object();

看到这里就明白了,和我们前面说的一样,HashSet是用HashMap来保存数据,而主要使用到的就是HashMapkey
看到private static final Object PRESENT = new Object();。这里使用一个静态的常量Object类来充当HashMapvalue,既然这里mapvalue是没有意义的,为什么不直接使用null值来充当value呢?
比如写成这样子private final Object PRESENT = null;我们都知道的是,Java首先将变量PRESENT分配在栈空间,而将new出来的Object分配到堆空间,这里的new Object()是占用堆内存的(一个空的Object对象占用8byte),而null值我们知道,是不会在堆空间分配内存的。
那么想一想这里为什么不使用null值。看一个异常类java.lang.NullPointerException,这绝对是Java程序员的一个噩梦,这是所有Java程序猿都会遇到的一个异常,你看到这个异常你以为很好解决,但是有些时候也不是那么容易解决,Java号称没有指针,但是处处碰到NullPointerException。所以啊,为了从根源上避免NullPointerException的出现,浪费8byte又怎么样

1.3 构造方法

  /**       * 使用HashMap的默认容量大小16和默认加载因子0.75初始化map,构造一个HashSet
       */ 
            public HashSet() {
          map = new HashMap<E,Object>();
      }
  
      /**       * 构造一个指定Collection参数的HashSet,这里不仅仅是Set,只要实现Collection接口的容器都可以
      */
           public HashSet(Collection<? extends E> c) {
         map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 1, 16));
        // 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中
                addAll(c);
     }
 
     /**      * 使用指定的初始容量大小和加载因子初始化map,构造一个HashSet
      */
           public HashSet( int initialCapacity, float loadFactor) {
         map = new HashMap<E,Object>(initialCapacity, loadFactor);
     }
 
     /**      * 使用指定的初始容量大小和默认的加载因子0.75初始化map,构造一个HashSet
      */     public HashSet( int initialCapacity) {
         map = new HashMap<E,Object>(initialCapacity);
     }
 
     /**     * 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义
      */
          HashSet( int initialCapacity, float loadFactor, boolean dummy) {
         map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
 }

从构造方法可以很轻松的看出,HashSet的底层是一个HashMap,理解了HashMap后,这里没什么可说的。只有最后一个构造方法有写区别,这里构造的是LinkedHashMap,该方法不对外公开,实际上是提供给LinkedHashSet使用的,而第三个参数dummy是无意义的,只是为了区分其他构造方法。

1.4 增加和删除

    /**
     * 利用HashMap的put方法实现add方法
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)== null;
    }

    /**
     * 利用HashMap的remove方法实现remove方法
     */
    public boolean remove(Object o) {
        return map .remove(o)==PRESENT;
    }

    /**
     * 添加一个集合到HashSet中,该方法在AbstractCollection中
     */
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
       
       for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
    
    /**
     * 删除指定集合c中的所有元素,该方法在AbstractSet中
     */
    public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
        boolean modified = false;
        // 判断当前HashSet元素个数和指定集合c的元素个数,目的是减少遍历次数
        if (size() > c.size()) {
            // 如果当前HashSet元素多,则遍历集合c,将集合c中的元素一个个删除
            for (Iterator<?> i = c.iterator(); i.hasNext(); )
                modified |= remove(i.next());
        } else {
            // 如果集合c元素多,则遍历当前HashSet,将集合c中包含的元素一个个删除
            for (Iterator<?> i = iterator(); i.hasNext(); ) {
                if (c.contains(i.next())) {
                    i.remove();
                    modified = true;
                }
            }
        }
        return modified;
}

1.5 是否包含

      /**       * 利用HashMap的containsKey方法实现contains方法
       */ 
            public boolean contains(Object o) {
          return map .containsKey(o);
      }
        
      /**       * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中
      */
           public boolean containsAll(Collection<?> c) {
        // 取得集合c的迭代器Iterator
                Iterator<?> e = c.iterator();
        // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false
                while (e.hasNext())
            if (!contains(e.next()))
                return false;
         return true;
}

由于HashMap基于hash表实现,hash表实现的容器最重要的一点就是可以快速存取,那么HashSet对于contains方法,利用HashMapcontainsKey方法,效率是非常之快的。

1.6 容量检查

  /**       * Returns the number of elements in this set (its cardinality).
       *
       * @return the number of elements in this set (its cardinality)
       */ 
            public int size() {
          return map .size();
      }
  
     /**      * Returns <tt>true</tt> if this set contains no elements.
      *
      * @return <tt> true</tt> if this set contains no elements
      */
           public boolean isEmpty() {
         return map .isEmpty();
}

以上代码都很简单,因为基本都是基于HashMap实现

2 迭代器

2.1 HashMap的迭代器

我们看到Collection实现了Iterable接口,并且要求其子类实现一个返回Iterator接口的iterator()方法。那么既然HashSetCollection的孙子类,那么HashSet也应该实现了一个返回Iterator接口的iterator()方法,对不对,我们去看看

      /**       * Returns an iterator over the elements in this set.  The elements
       * are returned in no particular order.
       *
       * @return an Iterator over the elements in this set
       * @see ConcurrentModificationException
       */ 
            public Iterator<E> iterator() {
          return map .keySet().iterator();
}

HashSetiterator()方法竟然也是利用HashMap实现的

 public Set<K> keySet() {
         Set<K> ks = keySet;
         return (ks != null ? ks : (keySet = new KeySet()));
}

HashMapkeySet()方法的返回值竟然是一个Set,具体实现是KeySet

  private final class KeySet extends AbstractSet<K> {
          public Iterator<K> iterator() {
              return newKeyIterator();
          }
          public int size() {
             return size ;
          }
          public boolean contains(Object o) {
              return containsKey(o);
         }
         public boolean remove(Object o) {
             return HashMap.this.removeEntryForKey(o) != null;
         }
         public void clear() {
             HashMap. this.clear();
         }
}

KeySet是一个实现了AbstractSetHashMap的内部类。而KeySetiterator()方法返回的是一个new KeyIterator()方法

 Iterator<K> newKeyIterator()   {
         return new KeyIterator();
 }

new KeyIterator()方法返回的又是一个KeyIterator()方法

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


  private abstract class HashIterator<E> implements Iterator<E> {
          // 下一个需要返回的节点 
                   Entry<K,V> next;   // next entry to return 
                            int expectedModCount ;     // For fast-fail 
                                     int index ;          // current slot
          // 当前需要返回的节点 
                   Entry<K,V> current;// current entry  
          HashIterator() {
             expectedModCount = modCount ;
             if (size > 0) { // advance to first entry
                              Entry[] t = table;
               // 初始化next参数,将next赋值为HashMap底层的第一个不为null节点
                               while (index < t.length && ( next = t[index ++]) == null)
                     ;
             }
         }
 
         public final boolean hasNext() {
             return next != null;
         }
 
         final Entry<K,V> nextEntry() {
             if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();
             // 取得HashMap底层数组中链表的一个节点
                         Entry<K,V> e = next;
             if (e == null)
                 throw new NoSuchElementException();
 
             // 将next指向下一个节点,并判断是否为null32             if ((next = e.next) == null) {
                 Entry[] t = table;
                 // 如果为null,则遍历真个数组,知道取得一个不为null的节点
                                  while (index < t.length && ( next = t[index ++]) == null)
                     ;
             }
            current = e;
            // 返回当前节点
                         return e;
         }
 
         public void remove() {
             if (current == null)
                 throw new IllegalStateException();
             if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();
             Object k = current.key ;
             current = null;
             HashMap. this.removeEntryForKey(k);
             expectedModCount = modCount ;
         }
 
}

HashIterator这个类(也是HashMap的内部类),其中nextEntry()这个方法,该方法主要思路是,首选拿去HashMap低层数组中第一个不为null的节点,每次调用迭代器的next()方法,就用该节点next一下,当当前节点next到最后为null,就拿数组中下一个不为null的节点继续遍历。什么意思呢,就是循环从数组第一个索引开始,遍历整个Hash

2.2 LinkedHashMap的迭代器

看完HashMapIterator实现,再来看下LinkedHashMap是怎么实现的吧(直接看最核心代码)

  private abstract class LinkedHashIterator<T> implements Iterator<T> {
         // header.after为LinkedHashMap双向链表的第一个节点,因为LinkedHashMap的header节点不保存数据 
                 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;
        }
 
         public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
 
             LinkedHashMap. this.remove(lastReturned .key);
             lastReturned = null;
             expectedModCount = modCount ;
        }

        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
             if (nextEntry == header)
                 throw new NoSuchElementException();
 
             // 将要返回的节点nextEntry赋值给lastReturned
             // 将nextEntry赋值给临时变量e(因为接下来nextEntry要指向下一个节点)
                          Entry<K,V> e = lastReturned = nextEntry ;
             // 将nextEntry指向下一个节点
                         nextEntry = e.after ;
             // 放回当前需返回的节点
                          return e;
        }
}

可以看出LinkedHashMap的迭代器,不在遍历真个Hash表,而只是遍历其自身维护的双向循环链表,这样就不在需要对数组中是否为空节点进行的判断。所以说LinkedHashMap在迭代器上的效率面通常是高与HashMap的,既然这里是通常,那么什么时候不通常呢,那就是HashMap中元素较少,分布均匀,没有空节点的时候。

posted @ 2021-11-12 16:38  上善若泪  阅读(65)  评论(0编辑  收藏  举报