一、概述
HashSet类是常用的Set集合类,底层是采用HashMap来实现的,我们通常利用HashSet类的元素不重复的特性来达到去重的效果,那么跟随源码去一探究竟吧。
二、源码分析
(1) 类的声明
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
在类的声明中我们可以看到,HashSet类是继承自AbstractSet类,HashSet类可以复用父类的一些方法,同时HashSet类还是实现了Set接口,以及Cloneable接口和Serializable。
* AbstractSet继承自AbstractCollection,实现了Set接口,此类并没有重写 AbstractCollection 类中的任何实现(包括add()方法)。它仅仅添加了 equals 和 hashCode 的实现。
* 实现了Set接口表示本类是一个不包含重复元素的类,并最多包含一个null值。
* 实现Cloneable接口表示可以调用Object.clone方法返回该对象的浅拷贝。
* 实现Serializable接口表示可以启用其序列化功能,能通过序列化去传输。
(2) 成员变量
//序列化标记ID
static final long serialVersionUID = -5024744406713321676L;
//实际存放元素的map,类型是HashMap,所以HashSet是依赖HashMap的
private transient HashMap<E,Object> map;
//final修饰的不可改变的空对象
private static final Object PRESENT = new Object();
底层通过HashMap来进行数据的存储,通过下面的源码可以得知采用HashMap的key来存储元素,value统一存储一个不可改变的空对象PRESENT,这样既保证HashMap的存储结构,数据的移除操作也是通过HashMap的remove方法进行移除,而HashMap的remove方法会返回被移除的Value值,而把PRESENT设为final就能保证可以通过判断返回的值是否是PRESENT对象就可以判断是否移除成功。
(3) 构造方法
//无参构造函数
public HashSet() {
//初始化map为一个空的HashMap
map = new HashMap<>();
}
//带容量的构造函数
public HashSet(int initialCapacity) {
//初始化一个容量参数为initialCapacity的HashMap,但是最后map的容量不一定是initialCapacity,而是大于等于initialCapacity的最小2次幂
map = new HashMap<>(initialCapacity);
}
//带容量和负载因子的构造函数
public HashSet(int initialCapacity, float loadFactor) {
//在上一个方法基础上,多了设置负载因子这一步
map = new HashMap<>(initialCapacity, loadFactor);
}
//带容量和负载因子以及标记dummy的构造函数,但是没有public修饰。dummy标记是用来区分HashSet(int initialCapacity, float loadFactor)方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
//传入一个集合c的构造函数
public HashSet(Collection<? extends E> c) {
//创建一个足够大的map,容量计算规则是取(c.size()/.75f)+1和16取较大的值作为initialCapacity,但是这也不是最终的容量,最终的容量还是大于initialCapacity的最小2次幂
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
//添加集合c中的所有元素到map中
addAll(c);
}
(4) add()方法
//添加一个元素
public boolean add(E e) {
//调用HashMap的put方法,因为HashMap的Key不能重复,重复时会把添加的元素直接返回,成功则返回null
return map.put(e, PRESENT)==null;
}
HashSet类为什么能去重呢?它的add()方法告诉了我们答案,HashSet在调用set()方法的时候,是把添加的元素作为了HashMap的key,而value就是上面final修饰的空对象PRESENT。我看到这儿我惊了, HashSet利用HashMap的key不能重复的机制,因为key重复就覆盖value,以此来达到去重的效果,我只能说绝了!
(5) 其余核心方法
//调用HashMap的clear方法来清空元素
public void clear() {
map.clear();
}
//调用HashMap的containsKey来判断是否包含元素o
public boolean contains(Object o) {
return map.containsKey(o);
}
//通过调用HashMap的isEmpty方法来判断集合是否为空
public boolean isEmpty() {
return map.isEmpty();
}
//返回迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
}
//调用HashMap的remove方法来移除元素
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
//HashMap的大小就是集合的大小
public int size() {
return map.size();
}
HashSet类的其他方法也比较简单,都是直接调用HashMap的方法,如果你对HashMap还不熟,那我推荐你看一下《我的jdk源码(十三):HashMap 一磕到底,追根溯源!》。
三、总结
HashSet类还是有几点比较重要的知识点,罗列如下:
* HashSet底层依赖HashMap,所以也是线程不安全的。
* HashSet底层依赖HashMap,所以支持动态扩容,但也因此和HashMap一样,无法保证元素的有序性。
* 我们可以利用HashSet的元素不重复特性来达到为集合去重的效果。
更多精彩内容,敬请扫描下方二维码,关注我的微信公众号【Java觉浅】,获取第一时间更新哦!