HashSet去重原理

一、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都相等,证明重复了,则新值会覆盖旧值,保证同样的值只有一份,达到同样的值只有一份。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值