简介
HashSet是一个没有重复元素的集合,其本质是一个HashMap。
源码分析
对HashSet的操作,实质上都是对其核心的HashMap的操作。所以如果之前了解了HashMap的特性和工作原理,自然就知道了HashSet的工作原理。
继承实现关系
核心成员变量
map
private transient HashMap<E,Object> map;
HashSet的核心是一个HashMap,它利用HashMap的key不重复的特性,以保证HashSet中的元素不重复。其实,我们保存在HashSet里的值,都是内部这个HashMap的key。
PRESENT
private static final Object PRESENT = new Object();
虽然HashSet需要的是内部这个HashMap的key值,但是key也得有对应的value嘛。所以每一个key的value都默认为这个PRESENT。
构造方法
HashSet有五个构造方法
无参构造方法
public HashSet() {
map = new HashMap<>();
}
只是初始化了内部的map
接收一个Collection参数
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
初始化内部的map,同时初始化map的容量。这个容量与传入的Collection大小有关。初始容量为什么这么计算呢?这篇文章Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例里的解释比较好:
首先,说明(c.size()/.75f) + 1。因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,就需要将HashMap的容量翻倍。所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。接下来,说明为什么是 16 。HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。所以,这里指定为16是从性能考虑。避免重复计算。
之后调用了继承的抽象方法addAll,我们来看看addAll中做了哪些处理:
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
这里循环调用了add方法,而HashSet的add方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
将Collection中的每一个元素当做keyput进核心map。
接收初始容量的构造方法
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
实际上是初始化了一个容量大小为入参值的map
接收初始容量和自定义加载因子的构造方法
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
实际上是初始化了一个容量大小和加载因子为入参值的map
接收三个入参的构造方法
这个构造方法很奇怪……接受了三个入参,但是实际上第三个并没有使用。依旧只是初始化了一个容量大小为入参值的map。官方回复:忽视它。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
其他方法
这里就把HashSet的其他方法一并看了,因为,都是包装了一层对HashMap方法……在此不再赘述。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public void clear() {
map.clear();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean isEmpty() {
return map.isEmpty();
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public int size() {
return map.size();
}