1、api
此类实现
Set
接口,由哈希表(实际上是一个
HashMap
实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用
null
元素。
此类为基本操作提供了稳定性能,这些基本操作包括
add
、
remove
、
contains
和
size
,假定哈希函数将这些元素正确地分布在桶中。对此 set 进行迭代所需的时间与
HashSet
实例的大小(元素的数量)和底层
HashMap
实例(桶的数量)的“容量”的和成比例。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
注意,此实现不是同步的。
如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它
必须
保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用
Collections.synchronizedSet
方法来“包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:
Set s = Collections.synchronizedSet(new HashSet(...));
此类的
iterator
方法返回的迭代器是
快速失败
的:在创建迭代器之后,如果对 set 进行修改,除非通过迭代器自身的
remove
方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出
ConcurrentModificationException
。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来在某个不确定时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器在尽最大努力抛出
ConcurrentModificationException
。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:
迭代器的快速失败行为应该仅用于检测 bug。
2、源码学习
HashSet在内部维护了一个HashMap,并且只使用到这个HashMap的key相关的方法
private transient
HashMap<
E
,
Object>
map
;
而所有的value都是指向了一个名为PRESENT的对象
// Dummy value to associate with an Object in the backing Map
private static final
Object
PRESENT
=
new
Object()
;
例如如下方法,实际都是操作了在HashSet内部维护的HashMap对象:
public int
size
() {
return
map
.size()
;
}
public boolean
add
(
E
e) {
return
map
.put(e
,
PRESENT
)==
null;
}
public boolean
remove
(Object o) {
return
map
.remove(o)==
PRESENT
;
}