HashSet如何保证元素不重复

HashSet保证元素不重复?
说直接一点,其实HashSet的add() 方法中调用的是HashMap的put() 方法. 我们都知道的是Map的key不允许重复, 这其实就是HashSet能够保证元素不重复的真正原理.

稍微跟入源码观察一下
Class HashSet
/**
	先看看add方法的实现
*/
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
/**
	有些人会问PRESENT是哪里来的参数
*/        
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

解释一下?
没什么好解释的
我们能够看到的是,PRESENT作为一个不可修改的对象, 在put方法中当做value传入这样我们只需要关注的就是 map的key的特性 及map中key不允许重复,并且只能有一个null值存在.

至于为什么map的key不允许重复,我们继续深入hashMap的put() 方法看一下

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//putVal()
//onlyIfAbsent是true的话,不要改变现有的值
//evict为true的话,表处于创建模式 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果主干上的table为空,长度为0,调用resize方法,调整table的长度
        if ((tab = table) == null || (n = tab.length) == 0)
            /* 这里调用resize,其实就是第一次put时,对数组进行初始化。
               如果是默认构造方法会执行resize中的这几句话:
               newCap = DEFAULT_INITIAL_CAPACITY;  新的容量等于默认值16
               newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);            
               threshold = newThr;   临界值等于16*0.75
               Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 
               table = newTab; 将新的node数组赋值给table,然后return newTab
                
                如果是自定义的构造方法则会执行resize中的: 
                int oldThr = threshold;   
                newCap = oldThr;   新的容量等于threshold,这里的threshold都是2的倍数,原因在    
                于传入的数都经过tableSizeFor方法,返回了一个新值
                float ft = (float)newCap * loadFactor; 
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE); 
                 threshold = newThr; 新的临界值等于 (int)(新的容量*负载因子)
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
                table = newTab; return newTab;
            */
            n = (tab = resize()).length;  //将调用resize后构造的数组的长度赋值给n
        if ((p = tab[i = (n - 1) & hash]) == null) //将数组长度与计算得到的hash值比较
            tab[i] = newNode(hash, key, value, null);//位置为空,将i位置上赋值一个node对象
        else {  //位置不为空
            Node<K,V> e; K k;
            if (p.hash == hash &&  // 如果这个位置的old节点与new节点的key完全相同
                ((k = p.key) == key || (key != null && key.equals(k)))) 
                e = p;             // 则e=p
            else if (p instanceof TreeNode) // 如果p已经是树节点的一个实例,既这里已经是树了
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {  //p与新节点既不完全相同,p也不是treenode的实例
                for (int binCount = 0; ; ++binCount) {  //一个死循环
                    if ((e = p.next) == null) {   //e=p.next,如果p的next指向为null
                        p.next = newNode(hash, key, value, null);  //指向一个新的节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 如果链表长度大于等于8
                            treeifyBin(tab, hash);  //将链表转为红黑树
                        break;
                    }
       if (e.hash == hash &&  //如果遍历过程中链表中的元素与新添加的元素完全相同,则跳出循环
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e; //将p中的next赋值给p,即将链表中的下一个node赋值给p,
                           //继续循环遍历链表中的元素
                }
            }
            if (e != null) { //这个判断中代码作用为:如果添加的元素产生了hash冲突,那么调用                
                             //put方法时,会将他在链表中他的上一个元素的值返回
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)  //判断条件成立的话,将oldvalue替换        
                //为newvalue,返回oldvalue;不成立则不替换,然后返回oldvalue
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;  //记录修改次数
        if (++size > threshold)   //如果元素数量大于临界值,则进行扩容
            resize();
        afterNodeInsertion(evict);  
        return null;
    }

代码中,展示的是putVal() 方法,但是仔细阅读源码我们不难发现,putVal() 方法很多逻辑都是在定位哈希桶数组的位置,通过indexOf() 获取到key在数组中的位置,并且判断该位置上是否有值,有值则默认相同,然后将value值替换

PS:jdk1.7 和 jdk1.8的HashMap类有了很大的变化,如果想深入了解的话,请走下面传送门
HashMap详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值