HashSet介绍

image.png

  1. 实现了Serializable接口,表明它支持序列化。
  2. 实现了Cloneable接口,表明它支持克隆,可以调用超类的clone()方法进行浅拷贝。
  3. 继承了AbstractSet抽象类,和ArrayList和LinkedList一样,在他们的抽象父类中,都提供了equals()方法和hashCode()方法。它们自身并不实现这两个方法,(但是ArrayList和LinkedList的equals()实现不同。你可以看我的关于ArrayList这一块的源码解析)这就意味着诸如和HashSet一样继承自AbstractSet抽象类的TreeSet、LinkedHashSet等,他们只要元素的个数和集合中元素相同,即使他们是AbstractSet不同的子类,他们equals()相互比较的后的结果仍然是true。 ```java public boolean equals(Object o) {
    1. if (o == this)
    2. return true;
    3. if (!(o instanceof Set))
    4. return false;
    5. Collection<?> c = (Collection<?>) o;
    6. //必须保证元素的个数相等。
    7. if (c.size() != size())
    8. return false;
    9. try {
    10. //调用了AbstractCollection的方法。
    11. return containsAll(c);
    12. } catch (ClassCastException unused) {
    13. return false;
    14. } catch (NullPointerException unused) {
    15. return false;
    16. }
    }
public boolean containsAll(Collection<?> c) {
//只需要逐个判断集合是否包含其中的元素。
    for (Object e : c)
        if (!contains(e))
            return false;
    return true;
}

4. 实现了Set接口。
<a name="JfgY5"></a>
## 源码分析
**HashSet类**<br />我们可以看到HashSet有多个构造函数,但每个构造函数都是初始化了一个HashMap的对象,所以**HashSet内部是使用HashMap来存储对象的。**
```java

    /**
     * 构造一个新的空集合;
     * 备份HashMap实例具有默认的初始容量(16)和负载因子(0.75)。
     */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 构造包含指定集合中元素的新集合。
     * HashMap是使用默认加载因子(0.75)创建的,初始容量足以包含指定集合中的元素。
     *
     * @param c the collection whose elements are to be placed into this set
     * @throws NullPointerException if the specified collection is null
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    /**
     * 构造一个新的空集合;
     * 备份HashMap实例具有指定的初始容量和指定的负载因子。
     * @param      initialCapacity   the initial capacity of the hash map
     * @param      loadFactor        the load factor of the hash map
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero, or if the load factor is nonpositive
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    /**
     * 构造一个新的空集合;
     * 备份HashMap实例具有指定的初始容量和默认负载因子(0.75)。
     * 
     * @param      initialCapacity   the initial capacity of the hash table
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 构造一个新的空链接哈希集。(此包专用构造函数仅由LinkedHashSet使用。)
     * 备份HashMap实例是具有指定初始容量和指定负载因子的LinkedHashMap。
     *
     * @param      initialCapacity   the initial capacity of the hash map
     * @param      loadFactor        the load factor of the hash map
     * @param      dummy             ignored (distinguishes this
     *             constructor from other int, float constructor.)
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero, or if the load factor is nonpositive
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

HashSet中使用的HashMap,key为Set的元素类型,value为Object。

private transient HashMap<E,Object> map;

add(E e)

    /**
     * 如果指定的元素尚未存在,则将其添加到此集合。
     * 更正式地说,如果此集合不包含元素e2,则将指定的元素e添加到此集合,
     * 从而(e==null?e2==null:e.equals(e2))。
     * 如果此集合已经包含元素,则调用将保持集合不变并返回false。
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     **/
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

PRESENT的定义

private static final Object PRESENT = new Object();

PRESENT是一个静态的类对象,Object类型。所有HashSet的实例都共享这个对象。
也就是说,我们在向set中添加一个e元素的时候,实际上就是在像map添加一个(e, Object)的键值对。我们添加的元素e变成了map中的key,而value则都是Obeject对象。又因为map中key值是唯一的,而value是可以重复的。所以我们添加的e作为key的话,就可以保证添加成功的话,e一定是唯一的。这就实现了set的唯一性。

remove(Object o)

/**
* 从该集合中删除指定的元素(如果存在)。
* 更正式地说,如果这个集合包含这样一个元素,那么移除一个元素e,
* 使得(o==null?e==null:o.equals(e))。
* 如果此集合包含元素,则返回true(如果此集合因调用而更改,则返回等效值)。
* (一旦调用返回,此集合将不包含元素。)
**/
public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

Hashmap中移除一个key的话,会返回这个key值锁对应的value,而我们这里的map,所有的key的value都是同一个对象PRESENT,所以我们这里只需要判断map.remove(o)的返回值是不是PRESENT,就可以确定是否成功移除了。

terator()

hashSet没有get方法,想要获取HashSet的元素需要调用iterator()。因为添加的元素值都被map当成了key来存储,显然没有从map中获取单独一个key的方法,但是我们可以获取所有key,调用keySet方法即可。

/**
* 返回此集合中元素的迭代器。元素不按特定顺序返回。
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
    return map.keySet().iterator();
}

小结

  • hashSet内部用HashMap来存储元素
  • HashSet利用本身的值来计算hash值,因为值被当作hashmap的key,而hashmap是利用key来计算hash值的
  • 因为hashset将value当作key来存储,所以根据map的key值唯一的原理,我们就可以实现set的无重复元素的功能