源码分析——HashSet

Set集合类型虽然是属于Collection集合体系中的,但是其实现类都是使用Map集合体系中的实现类实现的。至于如何实现我们继续开始研究。

首先,再次亮出集合家族图谱:

源码分析——HashSet

从中可以看到Set接口下面有三个实现类,分别是:HashSet、TreeSet、LinkedHashSet,乍一看感觉和Map接口下的三个实现类:HashMap、TreeMap、LinkedHashMap有异曲同工之妙,我们进去向下探究。首先看看HashSet的继承体系:

源码分析——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);
		}
	}

参考资料:

https://www.cnblogs.com/tong-yuan/p/HashSet.html