浅析Java中HashSet源码

685 阅读3分钟

HashSet可以存放null值,但是只能有一个null,HashSet不保证元素是有序的,这个顺序取决于hash后,在确定索引的位置,它不能有重复元素/对象。 原因: HashSet是Set接口实现类,而HashSet实际上是HashMap(以下源码可以证明):

image.png

我们可以看到HashSet构造器引用了HashMap对象 而HashMap底层是:数组+链表+红黑树(这里不展开谈)

那么,HashSet添加元素,底层是怎么执行的呢?断点调试追一下源码分析一下。

image.png

1.首先进去调用了构造器 image.png

  1. 执行add()方法 image.png

3.执行put()方法 image.png 这里有个hash()方法,那么这个hash方法是什么呢?继续追进去看一下源码。

image.png 可以看到这个hash()方法内部,其实是返回key的hash值,但是不完全等价于hashcode,而是进行了一个无符号右移16位的处理得到的值。 `

   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 就是一个HashMap的一个数组,类型是Node[]
           //if语句 表示如果当前table 是null,或者大小为0,那么就进行第一次扩容
           //第一次扩容的大小是16个空间
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;//这个resize()方法就是对HashSet进行扩容。
        //1.根据Key得到hash 去计算 该Key应该存放到table表的哪个索引位置并把这个位置的对象,赋给p
        //2.怎么判断p是否为null
            1)如果p为null,表示还没存放元素,就创建一个Node
            2)就放在该位置tab【i】 = newNode(hash,key,value,null)
            
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        
        //如果满足下面条件的任意一个就不能加入HashSet:
        //1.准备加入的key和p都指向了Node 节点的Key是同一个对象
        //2. p指向的Node 结对是key 的equals() 和准备的key比较后相同
        
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
            //下面代码判断p 是不是一棵红黑树,如果是的话就调用putTreeVal,来进行添加          
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //如果table对应的索引位置,已经是一个链表,就是用for循环比较
            //(1)依次和该链表的各个元素作比较,若都不同,则加入到该链表的最后
            //     注意再把元素添加到链表后,立即判断 该链表是否已经达到8个结点
            //     如果已经达到,就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
            //     当然进化前也要判断这个table表有没有64个容量。
            
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;//记录修改次数
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}`

以上就是对putVal内部源码的具体分析了。

image.png

image.png