一、HashSet的主要特征:
1、实现了Collection接口的子类:set接口
2、HashSet的存储是无序的,即遍历的顺序和我们添加的顺序无关
3、HashSet底层的数据结构是哈希表。根据哈希表得出的哈希值代表该对象的存储位置
4、HashSet不能添加重复元素,是基于HashMap实现的
二、HashSet去重
了解了HashSet添加元素过程,就知道Hashset去重的原理
HashSet添加元素的执行流程是:把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现,会将对象插入到相应的位置中。如果有相同hasdcode值的对象,会调用对象的equals()方法来检查对象是否真的相同,如果相同,则HashSet就不会让重复的对象加入到HashSet中,这样就达到去重效果,保证元素不会重复。
HashSet添加方法的实现源码:
表示:HashMap中put()返回null时,表示添加成功
从HashSet添加的实现源码,它调用是的HashMap中的put方法,HashMap中的put实现源码:
返回值:如果插入位置没有元素则返回null,否则返回上一个元素
HashMap中的put()方法调用的是putVal()方法,putVal()方法的实现源码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义Node数组、Node、以及两个变量n和i
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果哈希表为空,调用resize()创建一个哈希表,并用变量n记录哈希表长度
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 通过(n-1)&hash计算初bucket(桶就是插入数组的位置)并赋值给p
// 如果当前位置不存在Node,则通过Node的构造方法插入一个新的Node
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 桶中至少存在一个元素
Node<K,V> e; K k;
// p指向当前桶链表第一个节点,通过hash值以及key判断是否相同,如果相同则将e指向p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果不相同,则判断当前桶是否构成红黑树,如果是则直接插入一个新的树节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 链表结构且第一个节点不相同,开始循环链表
for (int binCount = 0; ; ++binCount) {
// 将e指向p的下一个节点,并判断是否为链表的最后一个节点
if ((e = p.next) == null) {
// 为最后一个节点的情况下,生成一个新的Node节点放入链表尾部
p.next = newNode(hash, key, value, null);
// 判断当前链表长度是否为7(插入的新节点未+1,因此为7,转换红黑树为8)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 将链表转为红黑树并退出
treeifyBin(tab, hash);
break;
}
//该节点不为链表最后一个节点,判断值是否相同,相同直接退出循环,因为此时e指
向 p.next 即p.next是相同节点,需要替换
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 上述两种情况都不满足的情况下,p指向e,继续循环
p = e;
}
}
// e不为空的情况下,说明存在相同元素 需要替换,并将替换前的节点值返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 操作次数+1
++modCount;
//如果大于了阙值 需要扩容的大小
if (++size > threshold)
resize();
// 该方法在HashMap中是个空方法
afterNodeInsertion(evict);
return null;
}
从上述源码得出,当将一个键值对放入HashMap时,首先根据key的hashcode()返回值决定该Entry的存储位置。如果有两个key的hash值相同,则会判断这两个元素key的equals()是否相同,如果相同就返回true,说明是重复键值对,那么HashSet中add()方法的返回值是false,表示HashSet添加元素失败。因此,如果向HashSet中添加一个已存在的元素,新添加的集合元素不会覆盖已有元素,从而保证了元素的不重复。如果不是重复元素,put方法最终返回null,传递到HashSet的add方法就是添加成功。
HashSet底层是由HashMap实现的,它可以实现重复元素的去重功能,如果存储的是自定义对象必须重写它的hashcode和equals方法。HashSet元素去重是利用HashMap的put方法实现的,在存储之间先根据key的hashcode和equals判断是否已存在,如果存在就不再重复插入,保证了元素的不重复,达到去重的效果。
简单来说:
1、判断hash值是否相等,如果不相等,则不重复
2、如果hash值相等,通过equals对比key的字符串是否相等,如果不相等,则不重复
3、如果hash值和key都相等,证明重复了,则新值会覆盖旧值,保证同样的值只有一份,达到同样的值只有一份。