源码分析——HashSet
Set集合类型虽然是属于Collection集合体系中的,但是其实现类都是使用Map集合体系中的实现类实现的。至于如何实现我们继续开始研究。
首先,再次亮出集合家族图谱:
从中可以看到Set接口下面有三个实现类,分别是:HashSet、TreeSet、LinkedHashSet,乍一看感觉和Map接口下的三个实现类:HashMap、TreeMap、LinkedHashMap有异曲同工之妙,我们进去向下探究。首先看看HashSet的继承体系:
从上图可以看出HashSet继承了AbstractSet抽象类,实现了Set接口。
public class HashSet<E> extends AbstractSet<E>
implements Set<E>,Cloneable,java,io,Serializable
其中Set接口提供了与Set集合有关的一切方法,而Cloneable和Serializable接口则分别用于克隆和序列化。
接下来继续看HashSet类的成员变量:
static final long serialVersionUID = -5024744406713321676L;
//序列化标识符,用于反序列化的
private transient HashMap<E,Object> map;
//使用hashMap来实现的
private static final Object PRESENT = new Object();
//和TreeSet一样,用于填充键值对中的value值
发现它的静态成员变量并没有多少,常用的也就一个,一个是HashMap集合类对象map。还有一个是静态常量Object类的对象PRESENT。那么他们都如何使用呢?继续向下看。
构造器:
public HashSet(){
//构造器1,空参,则将创建空的HashMap
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c){
//构造器2,传入一个集合c,则先根据集合c大小创建一个相应大小的HashMap集合
//再调用addAll方法将c中的元素复制到HashMap中
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity,float loadFactor){
//构造器3,传入初始容量与加载因子,因此根据这两个参数构造一个HashMap
//从而构造HashSet集合
map = new HashMap<>(initialCapacity,loadFactor);
}
public HashSet(int initialCapacity){
//构造器4,传入初始容量,根据参数构造HashMap
map = new HashMap<>(initialCapacity);
}
HashSet(initialCapacity, float loadFactor, boolean dummy){
//构造器5,非公开的,用于创造一个有序的HashMap,此处是为LinkedHashSet留后路
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
HashSet有五个构造器,当参数为空参时,直接创建一个空参的HashMap赋给成员变量map即可;当参数为集合c时,先根据集合c的大小创建一个相应大小的HashMap赋给map再将c中的元素依次复制到HashSet中;当参数为初始容量与加载因子时,根据传进来的参数构造合适的HashMap再赋给map;当参数为初始容量时,则根据初始容量来构造合适的HashMap再赋值给map;当参数为初始容量、加载因子与LinkedHashMap构造标记dummy时,会构造一个LinkedHashMap实现类,这个构造器不在当前类中使用,而是为LinkedHashSet的构造方法留后路。因此实际上HashSet实际可以使用的是四个构造器。
从构造方法可以得知,HashSet的底层数据结构其实是HashMap,但是HashMap中装载的是键值对,而Set集合类只是用来放置单个元素的,那么如何解决两者的不对称问题呢?
那么开始从常用方法来探究HashSet如何实现的吧
1.add
public boolean add(E e){
//向HashSet中增加元素
return map.put(e, PRESENT) == null;
}
从上面代码中可以看出,HashSet中的插入方法是直接调用HashMap中的put方法的,但是插入键值对时,key值为传进来的插入元素e,而value值确实一个常量PRESENT。这个常量正式上面所说的Object类的一个对象,且本对象里面并没有什么内容。因此我们可以理解成将键值对中的value值统统用常量对象PRESENT填充,只使用其中key值,这样就可以达到元素不可重复的目的了(map集合中的key不可重复)。但是HashSet中的key可以为null。
2.remove
public boolean remove(Object o){
//将HashSet中的某一个元素移除
return map.remove(o) == PRESENT;
}
理解了上面add方法的解释,后面的就很容易理解了,HashSet中的增删改查等方法都是直接调用相应的HashMap中的方法完成的。
3.contains
public boolean contains(Object o){
//判断HashSet是否含有某一个元素
return map.containsKey(o);
}
contains方法,判断HashSet中是否包含了某一个元素。
4.size
public int size(){
//获得HashSet的大小
return map.size();
}
获得HashSet大小
5.isEmpty
public boolean isEmpty(){
//判断HashSet是否为空
return map.isEmpty();
}
判断HashSet是否为空
6.clear
public void clear(){
//清空HashSet集合
map.clear();
}
清空HashSet
也许你会发现HashSet没有get方法,其实HashSet无法实现get方法,因为首先它不是有序的,无法通过索引或者其他方式获取某一个位置的元素,其次它是由一个个单独的元素组成的,不能通过key获得value这样的get方法。因此get方法也就没有存在的必要了,但是如何获得HashSet中的元素呢?只有一个办法,通过迭代器了。
总结:HashSet是建立在HashMap上的集合类,它只用到了HashMap中键值对的key值,从而获得容器内元素不可重复的特性。
总源码
public class HashSet<E> extends AbstractSet<E>
implements Set<E>,Cloneable,java,io,Serializable
{
//HashSet和TreeSet类似,也是建立于HashMap之上,将其键值对中的key值用来
//存放元素,将其中的value值都默认设置为Object类PRESENT
static final long serialVersionUID = -5024744406713321676L;
//序列化标识符,用于反序列化的
private transient HashMap<E,Object> map;
//使用hashMap来实现的
private static final Object PRESENT = new Object();
//和TreeSet一样,用于填充键值对中的value值
public HashSet(){
//构造器1,空参,则将创建空的HashMap
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c){
//构造器2,传入一个集合c,则先根据集合c大小创建一个相应大小的HashMap集合
//再调用addAll方法将c中的元素复制到HashMap中
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity,float loadFactor){
//构造器3,传入初始容量与加载因子,因此根据这两个参数构造一个HashMap
//从而构造HashSet集合
map = new HashMap<>(initialCapacity,loadFactor);
}
public HashSet(int initialCapacity){
//构造器4,传入初始容量,根据参数构造HashMap
map = new HashMap<>(initialCapacity);
}
HashSet(initialCapacity, float loadFactor, boolean dummy){
//构造器5,非公开的,用于创造一个有序的HashMap,此处是为LinkedHashSet留后路
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public Iterator<E> iterator(){
//获得迭代器
return map.KeySet().iterator();
}
public int size(){
//获得HashSet的大小
return map.size();
}
public boolean isEmpty(){
//判断HashSet是否为空
return map.isEmpty();
}
public boolean contains(Object o){
//判断HashSet是否含有某一个元素
return map.containsKey(o);
}
public boolean add(E e){
//向HashSet中增加元素
return map.put(e, PRESENT) == null;
}
public boolean remove(Object o){
//将HashSet中的某一个元素移除
return map.remove(o) == PRESENT;
}
public void clear(){
//清空HashSet集合
map.clear();
}
@SuppressWarnings("unchecked")
public Object clone(){
//克隆操作
try{
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E,Object>) map.clone();
return newSet;
}catch(CloneNotSupportedException e){
throw new InternalError(e);
}
}
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
//写出操作
s.defaultWriteObject();
//写出其他隐藏的东西
s.writeInt(map.capacity());
//写出HashMap中的表容量
s.writeFloat(map.loadFactor());
//写出HashMap中的加载因子
s.writeInt(map.size());
//写出HashSet的大小
for(E e : map.keySet())
s.writeObject(e);
//遍历操作,将HashMap中的所有元素都写出
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException{
//读入操作
s.defaultReadObject();
//读入其他隐藏的东西
int capacity = s.readInt();
//读入HashMap中的表容量
if(capacity < 0){
//判断表容量是否合法
throw new InvalidObjectException("Illegal capacity :" + capcaity);
}
float loadFactor = s.readFloat();
//读入加载因子
if(loadFactor <= 0 || Float.isNaN(loadFactor)){
//判断加载因子是否合法
throw new InvalidObjectException("Illegal load factor :" + loadFactor);
}
int size = s.readInt();
//读入HashSet大小
if(size < 0){
//判断size是否合法
throw new InvalidObjectException("Illegal size:" + size);
}
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),HashMap.MAXIMUM_CAPACITY);
//计算获得新表容量
SharedSecrets.getJavaOISAccess().checkArray(s,Map.Entry[].class,HashMap.tableSizeFor(capacity));
//相应检查,那么检查的是什么呢??
map = ((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity,loadFactor)
: new HashMap<E,Object>(capacity,loadFactor);
//判断HashMap是LinkedHashMap还是普通的HashMap;并根据其种类构造相应的map;
for(int i = 0; i < size; i++){
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e,PRESENT);
}
//依次读取元素并加入新的map中
}
public Spliterator<E> spliterator(){
//获得分割器
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
}
参考资料: