HashSet 底层基于 HashMap 来实现,是一个不允许有重复元素并且无序的集合。HashSet 允许有 null 值,但是只能有一个。HashSet 不是线程安全的,如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。HashSet 继承自 AbstractSet,并且实现了 Set、Cloneable、Serializable 接口。
HashSet 还有一个子类,LinkedHashSet,LinkedHashSet 集合也是根据元素的 hashCode 值来决定元素的存储位置,但是它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序来保存的。也就是说,当遍历 LinkedHashSet 集合里的元素时,会按照元素插入顺序来访问元素。
总的来说,HashSet 只是在 HashMap 的基础上包装了一层,基本都使用的 HashMap 的方法。
成员变量
容器 map
private transient HashMap<E,Object> map;
使用 HashMap 的 key 保存 HashSet 中所有元素。
静态 map 值
// Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();
定义一个静态的 Object 对象作为上面定义的 HashMap 的 value。
由于 HashSet 底层使用的是 map 中的键来存储数据,那么在新增数据的时候,原来 map 的值全部使用这个静态的对象。即调用map.put(数据, PRESENT)
。
构造方法
无参构造:默认创建 HashMap
public HashSet() { map = new HashMap<>(); }
无参数的构造函数,此构造函数创建一个大小为 16,加载因子为 0.75 的 HashMap 用于存储数据(联系到前面学习的HashMap)。
指定初始化集合
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
为了避免扩容操作,首先需要对初始化集合的大小与默认大小 16 进行比较,取最大然后初始化 map。之后调用 addAll 方法将初始化集合中的元素添加到 HashSet 中的 map 的 key 中。
指定容量和负载因子
public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); }
指定容量
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
这里直接调用的是 HashMap 的构造方法(联系到前面学习的HashMap)。
由 HashSet 的成员变量和构造方法可以看出,HashSet 本质还是在内部维护一个 HashMap 对象,将所有的数据都交给 HashMap 进行处理。
成员方法
新增元素 add
public boolean add(E e) { return map.put(e, PRESENT)==null; }
将指定元素存入 HashSet,内部实现就是将指定元素作为 key,用常量对象 PRESENT 作为 value 存入 HashMap。
删除元素 remove
public boolean remove(Object o) { return map.remove(o)==PRESENT; }
从集合中删除指定的元素,如果集合包含指定的元素,则返回 true。同样调用的 HashMap 的 remove 方法。
是否包含元素 contains
public boolean contains(Object o) { return map.containsKey(o); }
contains 方法的目的是检查 HashSet 中是否存在元素。如果找到该元素,则返回 true,否则返回 false。本质上调用的 HashMap 的 containsKey 方法。
集合大小 size
public int size() { return map.size(); }
调用 HashMap 的 size 方法,返回当前集合的大小。
HashSet 是否是空的 isEmpty
public boolean isEmpty() { return map.isEmpty(); }
调用 HashMap 的 isEmpty 方法,判断当前集合是否是空的。
清空元素 clear
public void clear() { map.clear(); }
调用 HashMap 的 clear 方法,将当前集合清空。原理是遍历 HashMap 中的桶数组,将桶中的每个位置置为 null(联系到前面学习的HashMap)。
遍历 HashSet
获取迭代器 iterator
public Iterator<E> iterator() { return map.keySet().iterator(); }
本质上获取到的是内部维护的 map 的 keySet(键集)的迭代器对象。
LinkedHashSet
LinkedHashSet 是 HashSet 的子类,通过源码发现它是一个空壳,构造方法都调用 HashSet 的一个 default (没有使用访问修饰符修饰)构造方法 通过 HashSet 的这个 default 构造方法可知,LinkedHashSet 也是在 LinkedHashMap 基础之上包装了一层。
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
线程安全的使用
使用 Colletcions 这个工具类的 synchronizedSet 方法类创建个线程安全的 Set 集合。
public class HelloWorld { public static void main(String[] args) { HashSet<Object> hashSet = new HashSet<>(); Set<Object> synchronizedSet = Collections.synchronizedSet(hashSet); } }
笔记大部分摘录自《Java核心技术卷I》,含有少数本人修改补充痕迹。
参考文章:http://985.so/mmhca