目录
一、大致分析HashMap
HashMap的底层结构是哈希桶,哈希桶(链地址法/开散列法)是解决哈希表冲突的一种方法。哈希桶可以理解为盛放不同key链表的容器,在这里我们可以把每个key的位置看作是一个桶,桶里放了一个链表,故该方法也叫分离链接法。在HashMap中,采用数组+链表的方式来实现对数据的储存。当满足一定条件时,链表会转换为红黑树,保证插入/删除/查找时间复杂度为O(1)。
哈希桶示意图:
二、Hashmap源码分析
1.默认成员变量
哈希桶的默认大小(数组大小)为1<<4=16
默认的负载因子为0.75
当链表长度大于8且哈希桶的大小大于64时,链表会转化为红黑树
当红黑树的节点小于6时,红黑树会转化为链表
哈希桶的最大容量1<<30
2.构造方法
①
两个参数的构造方法,第一个为哈希桶容量,第二个为负载因子。哈希桶容量大于0且小于等于最大容量,负载因子大于0。针对于this.threshold=tableSizeFor(initialCapacity),表明哈希桶的容量发生了改变,具体怎么改变下面解释。
②
一个参数的构造方法,参数为哈希桶容量,负载因子默认为0.75。
③
无参数的构造方法,负载因子默认为0.75,默认没有分配哈希桶容量。问题来了,那么哈希桶容量是多少?下面解释
3.底层实现过程
看它的put方法:
拿到key之后调用了hash方法:
key传给hash后判断key,key为空返回0,key不为空,返回key.hashCode()^(h>>>16),我们发现在计算hash时hashCode结束后还异或了h>>>16,这样做是为了得到更均匀的数字。计算完hash后调用了putVal方法:
先看前面一点,将hash传给putVal方法,先创建了tab数组,没有内存。进入第一个if语句,n=(tab=resize()).length,n为数组的容量,调用了resize方法:
table没有内存→oldTab也没有内存→oldCap=0→oldThr=0(threshold没有赋值默认为0)→进入最后一个else语句,newCap=1<<<4=16→创建newTab数组,容量为16→赋值于table
即没有参数时,在第一次put时哈希桶容量默认为16。
hash有了,数组有了,该寻找下标,我们认为的下标=hash%table.length(),底层是这样的吗?
发现下标i=(tab.length-1)&hash,其实当哈希桶容量为2的次幂时,(tab.length-1)&hash=hash%table.length() ,也就是HashMap会保证哈希桶的容量为2的次幂。问题来了,当我们自己规定哈希桶容量时,怎么保证容量为的次幂。回想我们知道指定哈希桶容量时是否还有this.threshold=tableSizeFor(initialCapacity),这么一句话就是在改变哈希桶的容量为2的次幂。
具体看tableSizeFor这个方法:
这个方法时让容量去靠近最近的2次幂,例如规定容量为11,则转换后容量实际为16(转化后的容量要大于等于规定的容量)。
确定位置之后放元素:
数组下标元素为空,直接放进去;如果不为空,先判断是否是红黑树,是红黑树按照树的方式插入,不是的话按照链表尾插法插入。插入完成之后判断个数是否大于7,大于7考虑红黑树。
treeifyBin(tab,hash)方法:
当哈希桶容量大于64转变为红黑树,不大于64调用resize()方法扩容,容量变为之前的两倍。