Java容器HashMap底层分析

目录

1、HashMap底层如何存储?

2、HashMap底层成员变量介绍?

3、HashMap数组的初始化?

 通过源码我们可以发现putVal方法的实现非常复杂,但是我们这里只看红色方框框住的地方:

 我们可以看到resize方法的实现也很复杂,我们只看红色方框圈的地方:

4、HashMap底层如何计算hash值?



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 = tabletable上面讲过了就是数组用来存放元素的,而table并没有初始化所以table=nulltab = 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 = thresholdthreshold是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)

第四行跳过

if语句判断oldCap是否大于0,而我们上面说oldCap=0,所以if跳过,else if(oldThr>0),false跳过进入else语句,将刚刚创建的newCap = DEFAULT_INITIAL_CAPACITYDEFAULT_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数组初始化容量为newCapnewCap=16,所以newTab数组初始容量为16,然后将数组table指向newTab,此时table数组就初始化完成了

后面我的我们就不用看了因为都不满足,所以直接返回newTabnewTab就是我们初始化的数组

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,此时我们就得出了处理过后的哈希值,后面数组就会通过这个处理过后的哈希值进行存放元素

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值