目录
通过源码我们可以发现putVal方法的实现非常复杂,但是我们这里只看红色方框框住的地方:
我们可以看到resize方法的实现也很复杂,我们只看红色方框圈的地方:
1、HashMap底层如何存储?
HashMap底层是由数组和单向链表(哈希表)存储数据的,他们各有各的特点
数组:内存连续,查询效率高,增删效率低
单向链表:内存不连续,查询效率低,增删效率高
那么有没有一种结构是两个都占的呢?哈希表就是数组+链表的一种结构
2、HashMap底层成员变量介绍?
下面我们来介绍一下HashMap底层的成员变量的作用:
DEFAULT_INITIAL_CAPACITY就是代表数组默认初始化容量为16
MAXIMUM_CAPACITY就是代表数组的最大容量为2的30次方
DEFAULT_LOAD_FACTOR就是数组默认负载因数,当我们数组的容量占用75%时数组默认扩容
TREEIFY_THRESHOLD就是决定链表是否转为红黑树,当我们数组长度大于64时,为了提高哈希表的效率,会将数组中单向链表的节点个数大于8的转为红黑树,这个是判断单向链表长度是否超过8的
MIN_TREEIFY_CAPACITY就是数组长度大于64时,将节点转为红黑树,这个是判断数组长度是否超过64的
size就是哈希表的元素个数
table数组就是用来存放元素的
3、HashMap数组的初始化?
在JDK1.8过后的HashMap中对于数组的初始化方式采用的是延迟初始化的方式,这一点和ArrayList是一样的,什么是延迟初始化呢?就是当我们实例化HashMap容器时只是声明了一个HashMap对象,此时对象为null,只有当我们存储数据时才会开始初始化数组,对象才会引用这个数组,而HashMap是通过resize方法进行数组初始化的,那么我们来看看put方法:
通过ctrl+左键查看put方法的实现是调用了putVal方法并且传入了一些参数,hash方法就是计算key的哈希值的,key和value分别对应了我们传入的键与值,我们再看看putVal方法是如何实现的
通过源码我们可以发现putVal方法的实现非常复杂,但是我们这里只看红色方框框住的地方:
我们可以看到方法的第一行声明了很多临时变量:Node数组tab,Node对象p...
然后我们看第一个if判断,tab = table,table上面讲过了就是数组用来存放元素的,而table并没有初始化所以table=null,tab = table = null,那么if语句成立,这句话的意思就是判断此时数组是否有创建并且初始化,如果没有则成立
进入if语句,n = (tab = resize()).length来我们先看括号,tab = resize(),tab我们上面声明了是一个Node<K,V>的数组,那么resize方法返回的肯定是一个数组对象,此时我们点进resize方法看看
我们可以看到resize方法的实现也很复杂,我们只看红色方框圈的地方:
首先第一行声明oldTab数组等于table,table我们上面说过了,table=null,oldTab = null,
第二行有一串三元运算符,oldTab是否为空,true,那么oldCap等于0
第三行oldThr = threshold,threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
第四行跳过
if语句判断oldCap是否大于0,而我们上面说oldCap=0,所以if跳过,else if(oldThr>0),false跳过,进入else语句,将刚刚创建的newCap = DEFAULT_INITIAL_CAPACITY,DEFAULT_INITIAL_CAPACITY代表的就是默认初始化容量为16
因为DEFAULT_INITIAL_CAPACITY在被初始化为了1<<4,学过C语言的应该知道左移乘2,右移除2,那么1<<4就相当于2*2*2*2 = 16,所以newCap=16
newThr = DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY
所以newThr = (int)(0.75f * 16),newThr = 12
最后创建了一个newTab数组,初始化容量为newCap,newCap=16,所以newTab数组初始容量为16,然后将数组table指向newTab,此时table数组就初始化完成了
后面我的我们就不用看了因为都不满足,所以直接返回newTab,newTab就是我们初始化的数组
4、HashMap底层如何计算hash值?
哈希值我们前面有看到过,他就是用来计算哈希值然后通过哈希值放到数组的某个位置的,当然哈希值可能会是很大的一个数,所以会通过处理之后才会当做下标将元素放入数组中,那么哈希值是怎么处理的呢?让我们来看看源代码:
前面我们已经说过了,HashMap在存储数据时会调用hashCode方法,那么HashMap的put方法一定调用了hash方法对key进行哈希值的运算
ctrl+左键点进hash方法的源码看看
我们可以看到hash方法的实现非常简单,
首先判断key是否为空,如果为空返回0,如果不为空那么就调用key的hashCode方法计算出哈希值,然后将哈希值与哈希值的前16位进行异或运算
如果没有搞懂的话我们来用一个例子来帮大家理解,假设key调用的hashCode方法返回的是123456那么h = 123456,
h >>>16就是将二进制右移16位,
123456的二进制位为:0000 0000 0000 0001 1110 0010 0100 0000,
那么右移16位就等于:0000 0000 0000 0000 0000 0000 0000 0001
通过异或运算过后计算出123457,异或的规则就是相同为0,相异为1
计算出哈希值过后返回,通过putVal传参
putVal的形参hash = 123457,第一行第二行都是数组初始化的代码,我们直接看第二个if语句判断,if(p = tab[i = (n - 1) & hash] == null),刚刚传入的hash = 123457,tab数组就是存放元素的,n通过上面的初始化得到数组默认初始化容量为16,n = 16,
(n-1)& hash = 15 & 123457 = 1,此时我们就得出了处理过后的哈希值,后面数组就会通过这个处理过后的哈希值进行存放元素