解读HashMap和HashSet的源码

一、HashSet

(1)HashSet底层是HashMap
其实下面完全是因为hashmap的原因导致的
(2)添加一个元素时,先得到hash值–>会转成索引值
(3)找到存储数据表table,看到这个索引位置是否已经存放的有元素
(4)如果没有,直接加入
(5)如果有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后
(6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

HashSet的底层结构

在这里插入图片描述

HashSet的构造方法和add方法的源码如下

在这里插入图片描述
在这里插入图片描述
我们发现其实都是调用map的相关方法,add方法的本质会把元素当做map的key,而value是下面的object类型的常量:
在这里插入图片描述

由于一切都是跟HashMap挂钩的,那么分析HashMap的底层就尤为重要!

二、HashMap

HashMap的底层结构是:数组+单链表+红黑树
1)HashMap底层维护了Node类型的数组table,默认为null
2)当创建对象时,将加载因子(loadfactor)初始化为0.75.
3)当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有完素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
5)以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推.
6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),粗Btable的大小>= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

HashMap的底层结构

其实说白了底层就是维护下面Node类型的数组

在这里插入图片描述

构造方法分析

如果是无参构造,会给负载因子loadFactor赋予默认值0.75。注意!table依旧是空数组,在第一次添加元素时,才会扩容到16
在这里插入图片描述
在这里插入图片描述
如果是有参构造:
在这里插入图片描述

关键代码分析

HashMap的底层代码非常的复杂,这里我们只调出重点的地方说一下
分析增加元素的过程:其代码如下
在这里插入图片描述

其中hash(key)方法的源码如下图所示,从源码我们可以看出hash值并不是简单的hashCode()
在这里插入图片描述

1.其中最核心的代码就是putVal(),该方法是整个HashMap的核心,下面是我对源码的解读注释:
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)判断这个key是否为空
		//(2.1)如果key==null,表示还没存放过元素,就创建一个Node(k-v)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
		//(2.2)如果key!=null,表示该索引处存放过元素
        else {
			//一个开发技巧提示:在需要局部变量(辅助变量)的时候再创建
            Node<K,V> e; K k;//辅助变量
            if (p.hash == hash &&//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样 
			//并且满足下面的两点 
			//(1)准备加入的key 和 p指向的Node结点的key是同一个对象
			//(2)p指向的Node节点的key的equals()和准备加入的key比较后相同
			//就不能加入
                ((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);
			//p为链表的情况
            else {
				//(1)依次和该链表的的每一个元素比较
                for (int binCount = 0; ; ++binCount) {
					//都不相同,则加入到该链表的最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
						//注意在把元素添加到链表后,立刻判断该链表是否已经达到8个节点
						//如果是的,就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
						//注意,在转成红黑树时,treeifyBin()内部会判断tab大小是否小于64,如果是的话,先对tab进行扩容,如果不是才转成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            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;
        if (++size > threshold)//如果增添元素后,size达到了临界值(容积*负载因子),就会进行扩容操作
            resize();
        afterNodeInsertion(evict);
        return null;
    }
2.扩容代码的解析

触发扩容的情况:
(1)空参new出的hashmap第一次put元素
(2)增加元素后,size达到了临界值(容积*负载因子)
(3)链表长度达到8,且table的长度小于64,会发生扩容。扩容到64就会把链表转成红黑树

挑出核心代码进行分析,下面是第一次put元素时走的代码(因为此时table数组为空嘛)
在这里插入图片描述
后面我们会让临界值等于上面的newThr,并让table等于扩容后的newTab
在这里插入图片描述

3.树化代码解析

链表长度达到8就会触发下面的函数,如果此时table的长度小于64,则不会发生树化,先对table扩容。直到table大小扩容到64才会把链表转成红黑树。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

键盘歌唱家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值