hashSet 如何保证元素不重复的?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Stu_zkl/article/details/82714325

HashSet类中的add()源码

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
  }

类中map和PARENT的定义:

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object(); // 个人认为这是一个占位值,保证不为Null

HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。

以下是HashMap如何保证key值不重复的代码

注:HashMap如何存储null或为何能存储null值,源码分析

```
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true); // put 入口 ,当key为null是,当调用hash() //以下给出hash()函数 ,可以看到当为null时,赋值为0.
    }

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
```
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) {
        Node<K,V>[] tab;  //缓存底层数组用,都是指向一个地址的引用
        Node<K,V> p;     //插入数组的桶i处的键值对节点
        int n;   //底层数组的长度
        int i;   //插入数组的桶的下标    

         //刚开始table是null或空的时候,初始化个默认的table;为tab和n赋值,tab指向底层数组,n为底层数组的长度
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

         //(n - 1) & hash:根据hash值算出插入点在底层数组的桶的位置,即下标值;为p赋值,也为i赋值(不管碰撞与否,都已经赋值了)
        //如果在数组上,没有发生碰撞,即当前要插入的位置上之前没有插入过值,则直接在此位置插入要插入的键值对
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);//插入的节点的next属性是null
        else { //发生碰撞,即当前位置已经插入了值
            Node<K,V> e; K k;//中间变量,起到个值交换的作用

             //hash值相同,key也相同,那么就是更新这个键值对的值。同 jdk 1.7
            if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
              //jdk 1.8引入了红黑树来处理碰撞,上面判断p的类型已经是树结构了,
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果是,则走添加树的方法。
            else {
                for (int binCount = 0; ; ++binCount) { //还未形成树结构,还是jdk 1.7的链表结构
            //差别就是1.7:是头插法,后来的留在数组上,先来的链在尾上;1.8:是先来的就留在数组上,后来的链在尾上
            //判断p.next是否为空,同时为e赋值,若为空,则p.next指向新添加的节点,这是在链表长度小于7的时候
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

                    //如果在循环链表的时候,找到key相同的节点,那么就跳出循环,就走不到链表的尾上了。
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //这个就是p.next也就是e不为空,然后,还没有key相同的情况出现,那就继续循环链表,
                    // p指向p.next也就是e,继续循环,继续,e=p.next
                    p = e;
                    //直到p.next为空,添加新的节点;或者出现key相等,更新旧值的情况才跳出循环。
                }
            }

            //经过上面if else if else之后,e在新建节点的时候,为null;更新的时候,则被赋值。
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                  //onlyIfAbsent 这个在调用hashMap的put()的时候,一直是false,那么下面更新value是肯定执行的
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

其中最关键的一句:

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

调用了对象的hashCodeequals方法进行的判断,

1,如果hash码值不相同,说明是一个新元素,存;

2,如果hash码值相同,且equles判断相等,说明元素已经存在,不存;

3,如果hash码值相同,且equles判断不相等,说明元素不存在,存;


首先要明确:只通过hash码值来判断两个对象时否相同合适吗?答案是不合适的,因为有可能两个不同的对象的hash码值相同;

什么是hash码值?

在java中存在一种hash表结构,它通过一个算法,计算出的结果就是hash码值;这个算法叫hash算法;
hash算法是怎么计算的呢?
**是通过对象中的成员来计算出来的结果;
如果成员变量是基本数据类型的值, 那么用这个值 直接参与计算;
如果成员变量是引用数据类型的值,那么获取到这个成员变量的哈希码值后,再参数计算**

所以又得出一个结论:若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写

猜你喜欢

转载自blog.csdn.net/Stu_zkl/article/details/82714325