HashSet\HashMap的底层源码

HashSet\HashMap的底层源码

HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树

- HashSet添加元素底层是如何实现的?

1、添加一个元素时,先得到hash值 -会转成->索引值
2、找到存储数据表table,看这个索引位置是否已经存放的有元素
3、如果没有,直接加入
4、如果有,调用equals() 比较,如果相同,就放弃添加,如果不相同,则添加到最后
5、在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table(数组)的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

注意:
equals() 方法程序员自定义

源码解读

// 对HashSet 的源码解读
 //1. 执行 HashSet()
    public HashSet() {
        map = new HashMap<>();
    }
    
 //2. 执行 add()
    hashSet.add("java");
    
    public boolean add(E e) {//e = "java"
        return map.put(e, PRESENT)==null;//(static) PRESENT = new Object(); 占位
    }
    
 //3. 执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 
     public V put(K key, V value) {//key = "java" value = PRESENT 静态共享
        return putVal(hash(key), key, value, false, true);
     }
     //3.1 执行 hash(key) 得到key对应的hash值 
     static final int hash(Object key) {
     	int h;
     	// 算法h = key.hashCode()) ^ (h >>> 16) 
     	// 此hash值不是key.hashCode(), 为了防碰撞还做了处理
     	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  
 //4. 执行 putVal
 	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;

        /*(1)根据key得到的hash ,去计算该key应该存放到table表的哪个索引位置, 
             并把这个位置的对象,赋给 p
          (2)判断p 是否为null
          (2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
          (2.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; 
            
            /*如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
              并且满足下面两个条件之一:
              (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() 对当前这个链表进行树化(转成红黑树)
                        注意,在转成红黑树时,要进行判断, 判断条件
                        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                                resize();
                        如果上面条件成立,先table扩容.
                        只有上面条件不成立时,才进行转成红黑树
                    (2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                  */
                for (int binCount = 0; ; ++binCount) {
                    // 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null); 
                        // 注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                        if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                        	// 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                       /* 注意,在转成红黑树时,要进行判断, 判断条件
                        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                                resize();
                        如果上面条件成立,先table扩容.
                        只有上面条件不成立时,才进行转成红黑树
                        */
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接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;
        //size 就是我们每加入一个结点Node(k,v,h,next), size++
        // 当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size++
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict); //HashMap 留给子类去实现的方法
        return null;
    }

正常情况下插入正常情况下插入

- HashSet的扩容和转成红黑树机制

1.HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16加载因子 (loadFactor)是0.75 =12
2.如果table 数组使用到了临界值12,就会扩容到16
2=32,新的临界值就是32*0.75 =24,依次类推
3.在Java8中,如果一条链表的元素个数到达
TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
否则仍然采用数组扩容机制

链表上达到8个及以后,如果table数组没有达到64,就每添加一个,table数组就扩容两倍直至64,然后如果再添加,就进行树化。

自己debug进行验证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值