【本文是为了梳理知识的总结性文章,总结了一些自认为相关的重要知识点,只为巩固记忆以及技术交流,忘批评指正。其中参考了很多前辈的文章,包括图片也是引用,如有冒犯,侵删。】
目录
0 存储结构
从底层实现来看,HashSet 调用了HashMap来存储元素,不过之用到了Key,Value则用一个固定对象替代。由于Map中的key是不可重复的,这和Set不存在重复元素的特点刚好契合。使用HashMap也意味着不保证元素的顺序,允许使用 null 元素。
1 类定义
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
2 静态常量
由于HashSet只需要使用Key进行存储,因此Value存储的是一个虚拟值。
private static final Object PRESENT = new Object();
3 属性
底层调用HashMap对象进行存储和操作。
private transient HashMap<E,Object> map;
4 构造函数
主要是初始化HashMap,设置负载因子和初始容量。
默认构造方法
使用默认的HashMap构造方法,负载因子0.75,初始容量16。
public HashSet() {
map = new HashMap<>();
}
指定初始容量和负载因子的构造方法
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
指定初始容量的构造方法
使用默认负载因子0.75
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
使用LinkedHashMap的构造器
底层使用LinkedHashMap,并且是包私有的,只供LinkedHashMap使用。
// dummy只用于区分构造方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
使用指定容器构造HashSet的构造器
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
// 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中
addAll(c);
}
5 常用方法
底层的方法都是通过调用HashMap完成的代码都很简单,详情可以查看HashMap的代码JDK 8 HashMap 源码解析。
/**
* 返回set中元素的迭代器。调用hashMap的keySet来返回所有的key。
*/
public Iterator<E> iterator() {
return map.keySet().iterator();
}
/**
* 返回set中的元素的数量。调用HashMap的size()方法数量。
*/
public int size() {
return map.size();
}
/**
* 判断set是否为空。调用HashMap的isEmpty()进行判断。
*/
public boolean isEmpty() {
return map.isEmpty();
}
/**
* 判断set包含指定元素。底层实际调用HashMap的containsKey来判断
*/
public boolean contains(Object o) {
return map.containsKey(o);
}
/**
* 添加元素。如果set已包含该元素,则该调用不更改set并返回false。
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
/**
* 移除set中的指定元素。 如果此set已包含该元素,则返回true。
* 调用HashMap的remove方法删除指定Entry。
*/
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
/**
* 清空set中移除所有元素。调用HashMap的clear方法清空。
*/
public void clear() {
map.clear();
}
最佳实践
- 由于底层使用HashMap实现的,所以最好预估使用场景所需的容量,并设置为初始值,防止多次扩容,影响性能(尤其在数据量大的时候);
- 存储自定义对象的时候,重写equals方法和hashCode方法,以免出错;
- HashMap不是线程安全的,多线程环境下可以使用Collections.synchronizedSet()进行包装;