点击“奇舞移动技术”关注我们!
前言
Set作为常用的数据结构,它的最大特色是集合中的元素不会重复。我们常用Set来对List或者数组元素进行去重操作。本文主要分析HashSet类的工作原理和特点。
HashSet的继承关系
首先上类图:
一看类图,是不是有种熟悉的感觉?嗯,和ArrayList的类继承关系很相似。HashSet继承了AbstractSet抽象类,同时实现Set接口。而Set接口继承自Collection接口,AbstractSet抽象类继承自AbstractCollection抽象类。equals
、 hashCode
和 removeAll
方法。
和大多数集合类一样,AbstractSet同时实现了Cloneable和Serializable接口,即可以被克隆和序列化。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
源码分析
HashSet的源码很简单,因为它的内部实现都是基于HashMap的。
private transient HashMap<E,Object> map; // 不可序列化
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); // 虚设的Value值
可以看到,内部声明了一个HashMap属性,且它的Value类型是固定的Object。可能大家有疑惑,Set存储的数据可不是key-value的键值对,所以,HashSet直接给Value设了一个虚拟的Object值 PRESENT
,让Set只用关注Key,即我们存储的数据。
// 默认的HashMap配置:容量16,负载因子: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);
}
// 其他构造方法类似
...
不难想象,HashSet中其他诸如: size
、 isEmpty
等方法都是直接调用内部 map
的对应方法。这里主要说明一下: add
、 remove
和 iterator
方法。
public boolean add(E e) {
return map.put(e, PRESENT)==null; // Value值固定
}
HashSet中之所以元素不会重复,是利用了Map的相同的Key只会保存一份的特性。HashSet添加重复元素时,相对于把之前寸的值给覆盖了,只不过覆盖的值都是固定的 PRESENT
。
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
执行 remove
操作时,如果HashSet中没有该元素,对应的 map.remove
返回的会是null;如何存在,则返回固定的值 PRESENT
。
public Iterator<E> iterator() {
return map.keySet().iterator();
}
iterator
方法其实没有什么特别的,根据之前的分析,返回的肯定是Map的Key集合的迭代器。单独拿出来是想强调HashSet是没有get方法的。想要在HashSet中取出元素,要么通过迭代器,要么通过 toArray
方法。
总结
1.HashSet是通过HashMap实现的,代码实现很简单。利用了HashMap的Key只能保存一份的特性,实现了元素不可重复
2.HashSet可以存储null
3.HashSet是无序的,遍历顺序和元素的hashCode相关
4.HashSet不是线程安全的,多线程环境下可以使用: Sets=Collections.synchronizedSet(newHashSet(...));
--END--
识别二维码,关注我们