老规矩了,先看示例demo,再聊聊源码
import java.util.HashSet;
import java.util.Iterator;
public class HashSetTest {
public static void main(String[] args) {
HashSet hashset = new HashSet();
System.out.println("HashSet是否为空\t "+hashset.isEmpty());
hashset.add("hello world");
System.out.println("添加重复元素\t"+hashset.add("hello world"));
System.out.println("添加元素\t "+hashset.add("world hello"));
System.out.println("添加元素null\t"+hashset.add(null));
System.out.println("HashSet是否为空\t "+hashset.isEmpty());
System.out.println("HashSet大小"+hashset.size());
System.out.println("HashSet是否含有‘hello world’\t"+hashset.contains("hello world"));
System.out.println("HashSet是否含有‘world’\t"+hashset.contains("world"));
System.out.println("删除元素'hello world'\t"+hashset.remove("hello world"));
System.out.println("删除本不存在元素'world'\t"+hashset.remove("world"));
System.out.println("迭代器遍历");
Iterator iterator=hashset.iterator();
int i=1;
while(iterator.hasNext())
{
System.out.println("这是第"+i+"个元素\t"+iterator.next());
i++;
}
System.out.println("清空HashSet");
hashset.clear();
System.out.println("HashSet是否为空\t "+hashset.isEmpty());
System.out.println("HashSet大小"+hashset.size());
}
}
其结果如图
从运行的结果可以看出,HashSet有这么几个特点
1. 其集合元素都是唯一的,不存在重复的元素
2. 其集合元素中可以存在null,但有且只能有一个
3. 可以从迭代器遍历的结果中可以看出,HashSet是不会维护数据插入的顺序的,也不会对数据进行排序
好了,说完了特性,我们来聊聊其实现的源码吧!
1. 类继承结构
可以看出,HashSet主要继承了AbstractSet类(Set的抽象类),实现了Serializable(序列化接口)、Cloneable(克隆接口)、Set的接口
需要注意的是,HashSet其实现的常用方法都是覆盖了AbstractSet类中的方法,其实现都是调用了HashMap中的方法(这点下文有说明),不太明白的则是,为什么AbstractSet继承实现了Set接口,而HashSet也继承了Set接口(大概原因是类中的实现不能被继承下去吧???)(有读者知道这个答案的,请一定告知一声,鄙人感谢不尽!)
2. 类属性
static final long serialVersionUID = -5024744406713321676L; //序列化ID
private transient HashMap<E,Object> map; //底层数据结构是HashMap实现
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); //定义一个虚拟的object对象作为 HashMap 的value
其属性值以及其应用意义在后面注释中有说明了,因此不再另外阐述
注1:由于HashSet继承实现了Set接口,而Set规定了其集合中元素的唯一性,且也有需要有null值的需求,因此这才采用了HashMap作为其底层的数据结构。在这点上就体现了一个道理即“先有需求,后有其架构、实现“,望各位读者能明白,先分析需求才能完成后面的环节
注2:PRESENT的object对象是作为hashmap中的value而存在的,由于Set是一个单值集合,且具有唯一性,因此其map中的key都对应到该object上,即所有的key对应一个共有的object上,因此其前的修饰符为static
3. 构造函数
HashSet的构造函数比较多,且适应不同的用途
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);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
1. 无参构造函数
无参构造函数也比较简单,直接初始化new一个空的容量为16,装载因子为0.75的HashMap对象实例,并将其引用复制给map作为其实现的底层数据结构
2. 有参构造函数
2.1 初始容量的有参构造函数(int initialCapacity)
根据指定的容量值初始化一个HashMap实例,并将其引用复制给map作为其实现的底层数据结构
2.2 集合对象的有参构造函数(Collection <? extends E> c)
将集合存在的元素大小(而非集合的容量)除以0.75然后加1得到的数字和16进行比较,取其最大值,作为指定容量,初始化一个HashMap实例,调用addAll函数将集合元素添加进去
2.3 初始容量和装载因子有参构造函数(int initialCapacity,float loadFactor)
根据指定的容量大小和装载因子大小来初始化一个HashMap实例,并将其引用复制给map作为其实现的底层数据结构
2.4 初始容量和装载因子和标记的有参构造函数(int initialCapacity,float loadFactor,boolean dummy)
根据指定的容量大小和装载因子大小来初始化一个LinkedHashMap实例,并将其引用复制给map作为其实现的底层数据结构
注1:值得注意的是,其方法前没有任何修饰符,表示其为包权限的,对其不访问公开
Q1:为什么HashMap的容量为16,装载因子为0.75?
Q2:当以集合为形参调用有参构造方法时,为什么需要以集合大小除以0.75+1后和16比较,取其大值为容量初始化一个HashMap?
Q3:为什么有一个不对外公开的构造函数,且其底层结构为LinkedHashMap?仅仅是为了支持LinkedHashMap么?如果是为了支持LinkedHashMap,函数访问权限又为何不对外公开?其dummy参数又是什么意思?
4. 常用方法
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();
}
从源码可以看出,由于采用了HashMap作为其底层数据结构,所以常用的方法中都直接调用了map中的常用方法,这样一来,代码精简,功能也实现了,不得不佩服其作者高超编码能力和架构设计能力
4.1 size函数
调用了map中的size方法,返回其大小
4.2 isEmpty
调用了map中的isEmpty方法,返回其是否为空的结果
4.3 contains
传入object对象引用,调用了map中的containsKey方法,返回其是否包含有该object元素
4.4 clear
调用了map的clear方法,清除其所有元素
需要注意的是,add方法和remove方法
4.5 add方法
通过传入的E元素值作为key值,其PRESENT作为value值,通过调用了map的put方法,完成元素的添加,返回其值和null值对比后的结果
4.6 remove方法
传入object值,调用map的remove方法,和其PRESENT对比,返回其真假值
5. 常见的问题
5.1 HashSet有何特性?
5.2 HashSet的唯一性和null值是如何确保的?
5.3 为什么采用HashMap作为其底层数据结构?
5.4 HashSet是不是线程安全的?如果不是?Set线程安全的有哪些?
5.5 HashSet是不是维持其输入顺序的?如果不是?可采用哪个类维持其Set的输入顺序?
5.6 HashSet是不是有序的?如果不是?可采用哪个Set有序?