目录
前言
前面有提到,Set是不可重复的且无序的。这章我们要讲的的就是Set 的第一种实现方式, HashSet.
正文
类的描述
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable
这个类实现了 Set接口,支持一个哈希表(实际上 HashMap实例)。没有担保的迭代顺序组;特别是,它并不能保证订单将随时间保持不变。这个类允许 null元素。
这个类提供了持续的时间性能的基本操作(add、remove contains和size),假设哈希函数分散元素正确的桶中。遍历这组需要时间成比例的总和HashSet实例的大小(元素)的数量加上HashMap支持实例的“能力”(桶的数量)。因此,它是非常重要的不是设置初始容量太高(或负荷系数太低)如果迭代性能是很重要的。
注意,此实现不是同步的。如果多个线程同时访问一组散列,和至少一个线程修改设置,它必须同步外部。这通常是通过自然对象封装了一组同步。如果没有这样的对象存在,应该集“wrapped"使用Collections.synchronizedSet方法。最好在创建时完成,以防止意外的不同步访问设置:
集合s =集合。synchronizedSet(new HashSet(…));
这类的iterator方法返回的迭代器快速失败:如果设置修改创建迭代器后,任何时候以任何方式除非通过迭代器的remove方法,迭代器抛出ConcurrentModificationException。并发修改,因此,面对迭代器快速、清晰地失败,而不是冒着任意,不确定的行为在未来一个不确定的时间。
注意,迭代器的快速失败行为不能得到保证,一般来说,不可能努力做出任何担保的存在不同步的并发修改。快速失败迭代器扔ConcurrentModificationException力所能及。因此,编写一个程序,这将是错误的依赖这个异常的正确性:迭代器的快速失败行为应该只用来检测错误。
这个类是 Java Collections Framework的一员。
从以下版本开始:
1.2
另请参见:
Collection、 Set TreeSet、 HashMap Serialized Form
常量
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
根据常量和类描述,猜想HashSet其实是用HashMap实现的,因为HashMap Key 是不可重复的
下面所有相关的HashMap都会在研究HashMap的时候在详细解释,这里就大致提一下,有兴趣和苦恼的同学可以自己研究,和查资料
构造方法
//默认构造函数
public HashSet() {
map = new HashMap<>();
}
//初始化,并将一个集合初始化到当前集合
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c); //在添加中讲
}
//有默认容量和扩展因子的构造函数
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
//用默认容量的构造函数
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* Constructs a new, empty linked hash set. (This package private
* constructor is only used by LinkedHashSet.) The backing
* HashMap instance is a LinkedHashMap with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @param dummy ignored (distinguishes this
* constructor from other int, float constructor.)
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
//这个构造方法是用来给HashSet的资料LiskHashSet来用的,我们在下面的篇章会介绍
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
上面的构造函数,其实都是根据HashMap的构造函数来实现的, 关于
添加
//添加一个元素, 使用的hashMap的put, key为set当前元素,然后Value为常量 PRESENT
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//为什么要 == null,是因为HashMap在put进入新对象的时候会返回null, 如果原来存在key会返回原来元素
//而Set不可重复,所以通过 == null 来判断每次插入key是不是新的,和set里面的元素是不是重复的
// hashMap代码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
。。。。。。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
// hashSet中的addAll其实调用的是,父类的父类AbstractConllection的addAll方法
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e)) //这里使用了add方法,是HashSet自己实现的
modified = true;
return modified;
}
//所以addAll方法的原理就是使用调用当前循环调用当前类的add方法
移除
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
//调用hashMap的Map 为什么要判断==PRESENT呢
//是因为 hashMap在remove的时候会返回value, 而HashSet add的时候map的value一直都是PRESENT
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
结论
HashSet是通过HashMapKey不可重复来实现的key不重复,基本的操作都是通过hashMap来实现的,所以想要更好的理解HashSet, 我们就要深入了解HashMap, HashMap在接下来的Map相关的篇章里面会讲到,如果同学比较着急的话,可以自己研究,查找资料