更多Java容器源码分析可以参考:Java容器源码分析系列(持续更新中!)
(一)、概述
- HashSet是Java中常用的一个集合类,是Set接口的一个实现类,而Set接口又继承自Collection接口,所以HashSet同时也是Collection的实现类
- HashSet的底层实现是基于HashMap的,它利用HashMap中的key存储数据,使用成员变量PRESENT来填充value。
- 因为HashSet的方法非常少,主要都是基于HashMap实现的,如果对HashMap不了解,可以看一下:Java容器源码(五)——HashMap源码分析(基于JDK8)
(二)、类名
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
- HashSet继承自AbstractSet,同时实现了Set接口,Cloneable接口,Serializable接口
- AbstractSet:实现了Set接口。这样可以让HashSet只需要实现自己额外需要的方法,而不需要重复实现Map接口中的方法,实现了代码的复用。
- Map接口:这里只是起到一个标志作用,因为AbstractSet中已经实现过Set接口中的方法了。
- Cloneable接口:这里主要实现了浅克隆的功能。
- Serializable接口:这里主要是实现了序列化的功能。
(三)、成员变量
//用来存储数据的成员变量map,其实只使用它的key,value都用PRESENT来填充
private transient HashMap<E,Object> map;
// value的填充值,因为只需要HashMap的key
private static final Object PRESENT = new Object();
- HashSet中使用HashMap中的key来存储数据
(四)、构造方法
/**
* 无参构造方法,只是简单地创建一个HashMap
*/
public HashSet() {
map = new HashMap<>();
}
/**
* Collection为参数的构造方法
*/
public HashSet(Collection<? extends E> c) {
//根据传入的Collection的大小,对HashMap传入参数,创建HashMap
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
//往HashMap中添加所有元素
addAll(c);
}
/**
* 传入HashMap的初始容量以及负载因子
*/
public HashSet(int initialCapacity, float loadFactor) {
//将初始容量和负载因子作为参数创建HashMap
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* 根据传入的初始容量创建HashMap
*/
public HashSet(int initialCapacity) {
//将初始容量作为参数创建HashMap
map = new HashMap<>(initialCapacity);
}
/**
* 根据传入的参数创建LinkedHashMap
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
//根据传入的初始容量,负载因子创建LinkedHashMap
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
- 四个构造方法根据参数的不同创建不同的HashMap,进行初始化
(五)、add方法
public boolean add(E e) {
//将传入的元素e存入HashMap中
return map.put(e, PRESENT)==null;
}
- 如果返回false,代表HashSet中已经有这个元素了。返回true的话表示添加成功。
(六)、remove方法
public boolean remove(Object o) {
//将元素从HashMap中删除
return map.remove(o)==PRESENT;
}
- 如果返回PRESENT,则代表删除成功,如果返回null,代表删除失败
(七)、hashCode()与equals()
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个equals方法返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。