《Java源码分析》:HashSet

时间:2022-01-19 17:54:00

说明:我们都知道HashSet集合是不允许重复元素的,因此这个类的利用什么来保证这个集合里面不会有重复的元素呢?结论是:HashSet是借助于HashMap的key不允许重复这个特性来实现的。想想这个思想确实比较奇妙,是吧,HashMap是操作键值对,而HashSet是操作HashMap的key完成相关操作,或者这么说,HashSet全部的操作是借助于HashMap经过某种封装得到的

下面来开始具体的分析,其实,如果我们了解HashMap的内部实现,HashSet的内部实现就相当的容易理解。

1、HashSet的继承结构

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

HashSet继承了AbstractSet抽象类,也实现了Set、Cloneable和Serializable接口。

或许你可能有这样的疑惑:AbstractSet实现了Set接口,HashSet既要继承AbstractSet还需要实现Set接口呢??

如果你看过HashMap的源码,HashMap的继承结构和HashSet类似,,为了方便比较,还是贴一下:


public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
  • 1
  • 2
  • 3

在HashMap我们已经探讨了为什么AbstractMap实现了Map接口,HashMap又继承AbstractMap还要实现Map接口呢??

这个问题在HashMap源码分析中已经有了答案,因此,HashSet的继承结构为什么如此,我想原因应该是一样的,HashSet之所以还实现Set接口,在功能上没有任何作用,仅仅只是使结构体系更加清晰完整。

2、HashSet的属性和构造函数

从下面可以看出,HashSet有一个HashMap的引用,且有一个常量。

HashSet的构造函数,都是创建一个map实例对象。HashSet对象的默认大小与HashMap默认大小一样,是16,且大小只能是2的幂次方。

    private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/

public HashSet() {
map = new HashMap<>();
}

public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}

public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}

public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}

/**
* Constructs a new, empty linked hash set. (This package private
* constructor is only used by LinkedHashSet.) The backing
* HashMap instance is a LinkedHashMap with the specified initial
* capacity and the specified load factor.
*
*/

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3、HashSet中常见的一些方法

3.1、 add(E e)

源码如下:

    public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
  • 1
  • 2
  • 3
  • 4

看到这个源码是不是很爽,太简单了哈,就是将要添加的元素作为map的key,常量PRESENT作为value保存在HashMap对象中即可。有一点需要我们注意的是:这个add方法有返回值,返回值有map的put方法决定,当map调用put(key,value)方法时,如果存在key后,则map的put方法会返回此key对应的oldValue。此时add就会返回false

3.2、 iterator()

HashSet并没有提供类似于get的方法,只提供的获取访问元素的迭代器对象。iterator方法也是委托给了map,获取map的key集合的迭代器。

    public Iterator<E> iterator() {
return map.keySet().iterator();
}
  • 1
  • 2
  • 3
  • 4

3.3、 其它的方法

其它方法的源码如下,全部都是委托给HashMap来实现的,是不是很简单哈,这里就不再过多的进行分析。




public int size() {
return map.size();
}


public boolean isEmpty() {
return map.isEmpty();
}

public boolean contains(Object o) {
return map.containsKey(o);
}

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}

public void clear() {
map.clear();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

小结

确实比较简单哈,关于HashSet的内部实现我们只需要记住一点:是基于HashMap来实现的,即所有方法都是委托给HashMap对象来实现的;在使用方面,也只需要记住一点:所有Set集合对象存储的元素是不允许重复的哈