源码基于jdk 1.7.81
HashSet 简介
HashSet 是一个元素不能重复的集合。
HashSet 中当添加的元素有重复时,添加失败。
HashSet 是 Set 的一个实现类,而 Set 又继承了Collection 方法,并且没有添加多余的方法。
HashSet 继承了AbstractSet 类。
实现了 Cloneable 接口,说明它重写了clone()方法,可以被克隆。
实现了 java.io.Serializable 接口,说明它可以被序列化。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
HashSet 成员变量
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
map 存储的是键值对,在这里我们只用到了它的 key ,Object 是用来给它的 value 赋值的。所以 HashSet 拥有 HashMap 的所有特性,是用数组加链表的数据结构,并且每个 key 值是唯一的,在 HashSet 里体现的是它的值使唯一的。
HashSet 的构造函数
//默认构造函数
public HashSet() {
map = new HashMap<E,Object>();
}
//保存Collection 里面所有的数据
public HashSet(Collection<? extends E> c) {
// 创建map。
// 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?
// 首先,说明(c.size()/.75f) + 1
// 因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
// 当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
// 就需要将HashMap的容量翻倍。
// 所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
// 接下来,说明为什么是 16 。
// HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
// HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
// 所以,这里指定为16是从性能考虑。避免重复计算。
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
//增加 c 的元素在 map 里。
addAll(c);
}
//自动义容量和加载因子。
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
//自定义容量
public HashSet(int initialCapacity) {
map = new HashMap<E,Object>(initialCapacity);
}
//自动义容量和加载因子。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
HashSet 的构造方法都是调用 HashMap 的构造方法,学习HashSet 的时候可以对照着HashMap 来学习。
HashSet 的迭代器
//返回HashSet的迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
}
HashSet 的迭代器是调用了HashMap 通过键遍历的迭代器。
HashSet 的基本操作
/*
* 不能重复
* 去重
* key 允许为 null
* 线程不安全
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;//调用了HashMap的add方法
}
public V put(K key, V value) {
//当 key 为 null
if (key == null)
return putForNullKey(value);
//得到 hash 值
int hash = hash(key.hashCode());
//获得索引
int i = indexFor(hash, table.length);
//遍历索引位置的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果链表中已经存在当前的key,覆盖即可
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果当前链表没有就增加这个键值对到集合中
addEntry(hash, key, value, i);
return null; // 当原来没有这个元素时,返回null.
}
当被添加的元素已经存在在集合中,put 方法返回的 是原来的 value 值,在add 方法中,put 方法返回的 oldvalue 值 != null ,返回false,则添加失败。
当被添加的元素不存在集合中,添加元素在map 里,返回 null。