小程序判断数组的index是否为空_JAVA基础技术,HashMap(jdk1.8),作为程序员你应该熟悉的知识...

今天小高与各位猿友们分享JAVA基础知识,HashMap(jdk1.8),文章分为5部分,阅读全文约需要9分钟,废话不多说,直接上文。

01 hashMap的数据结构

JAVA基础知识jdk1.8中hashMap的数据结构是以数组+链表或者数组+红黑树的形式存在,介绍hashMap的数据结构之前,先介绍一下数组、链表、红黑树分别是什么。

①数组:

cbdbb6221dbf7b38edc2b793bf7469ef.png

数组是有序的元素序列,它在内存中的分布是连续的,对于这种数据结构index访问比较快,而非index插入比较慢。适用于根据index查询较多的场景。

②链表:

fb3518652e97bf0b8be6ab8705e66a12.png

链表是由一系列结点连接而成,这些结点在内存中的分布式离散的,对于这种数据结构随机插入比较快,时间复杂度为O(1)。适用于随机插入多的场景。

③红黑树:

f71f2c2de880b632e9af7883b29f1c61.png

红黑树属于二叉平衡查找树的一种,相同的数据结构还有AVL树,它的平衡策略要比AVL简单些,效率也高一些。在插入和查找的时间复杂度都为O(log(n))。

a6e59b8f6b09fdd320a0f1ef96b67f46.png

hashMap的数据结构继承了数组和链表的优点,可以根据元素key定位在数组中的位置,如果该位置有多个元素,可以用链表或红黑树将多个元素关联起来。

02 put过程

823ade32aa896c17b43f9705f3276832.png

(源图片链接:https://www.processon.com/view/link/5f03d152e0b34d4dba6f27f4)

流程介绍:

①向map中添加元素,初始map中的tab为空,则初始化tab,tab的默认长度为16。

②计算key的hash值,将hash值与数组的长度减1进行与运算,目的是求出key所在的数组下标,这里下标记作index。

③查看table[index]是否为空,若该位置为空,则根据key创建一个结点将该结点存入table[index];若不为空判断table[index]的key值和hash值与当前put的key值和hash值是否一致,一致则进行该结点的value值替换,然后返回old-value。

④若③步骤中table[index]的key值和hash值与当前put的key值和hash值不一致,则判断当前table[index]的数据结构是链表还是红黑树,若是链表则递归链表,取出每一个结点判断每一个结点key和hash值是否与当前put的key和hash值一致,如果一致则替换结点的value,否则根据put的key创建一个节点,并将该结点插入链表末尾。

⑤若④步骤中table[index]的数据结构为红黑树,则执行红黑树的put相关逻辑。(红黑树的相关逻辑分一篇单独讲,本文不再阐述)。

⑥插入完成后,判断当前数组元素的大小是否大于阈值(默认加载因子为0.75f,阈值=数组大小*加载因子,默认初始化阈值=16*0.75=12),若大小超过阈值,则进行数组扩容。

⑦数组扩容逻辑:如果当前数组大小大于等于2的30次幂,将加载因子赋值为2的31次幂减1,然后返回原始的数组;否则创建一个新的数组数组大小为老数组大小的两倍,加载因子也赋值为老加载因子的两倍。两层循环来将老数组里面的所有元素转移到新数组中,第一层循环是循环数组,第二层循环是循环每个数组中的链表或者红黑树,遍历到每一个结点将节点key的hash值与新数组长度减1重新进行与运算,得到该元素结点应该在新数组的位置,转移新数组的过程和上文中插入新数组的过程一直,不再赘述。转移完成后,将hashMap实例的table变量赋值为新的数组实例,扩容完成。

03 get过程

dc8b224383c2c08011344ceaeb2c9af8.png

(源图片链接:https://www.processon.com/view/link/5f03e27a5653bb2925cf96ee)

流程介绍:

①从map中根据key获取key的value,首先根据key计算出key的hash值,将hash值与数组长度减1进行与运算,目的是获得key所在的数组位置,记作index。

②判断table[index]的节点元素key和hash值是否与查找的key和hash值一致,若一致则返回该结点的value;否则判断table[index]的数据结构是红黑树还是链表。

③若②步骤判断的数据结构为红黑树则根据红黑树的查询逻辑进行数据查询;否则遍历这个数组位置上的链表,将每一个结点的key与hash值与查找的key的hash值做比较,若匹配成功则将该该节点的value值返回。如果遍历到链表末尾仍然没有找到匹配的结点,则返回null。

04 remove过程

00aabc04ea71d69b5b6bf423c1701cf6.png

(源图片链接:https://www.processon.com/view/link/5f03ef596376891e81005d85)

流程介绍:

①从map中根据key remove一个元素,第一步和get一样,先计算key的hash值,然后通过与运算获得key所在的数组下标,这里同样记作index。

②如果table[index]为空则返回null。

③如果②步骤判断table[index]不为空,则判断table[index]的数据结构是红黑树还是链表,若为红黑树,按照红黑树的逻辑来执行删除操作;否则遍历链表,将每个节点的key和hash值与删除的key和hash值比较,如果一致则将该结点的前驱结点的后继指针指向该节点的后继结点(这块有点绕,可以结合流程图中的的红色虚线框标记的地方理解),最后返回被删除结点元素的值。如果遍历到链表末尾仍然没有找到匹配的结点,则返回null。

④这里需要注意,添加元素有扩容的场景,但是移除元素table不会进行缩容。

05常见问题

Q1:jdk1.8中的hashMap如何解决hash冲突的?

A1:通过链地址法或者树形化来解决。当不同的元素被分配到数组同一个位置时,首先将这些元素节点用链表连接起来,如果链表长度大于等于8则将链表转化成红黑树。

Q2:hashMap为什么是线程不安全的?

A2:在hashMap进行put和remove的过程中没有进行同步,尤其是在扩容的时候,此时有hashMap并发操作容易造成扩容后数组数据错乱的情况。

Q3:在进行对hashMap进行put和remove操作时,都会对modCount变量做累加操作,这个变量的作用是什么?

A3:这个变量的作用是防止其他线程对hashMap的操作影响当前线程对hashMap的操作,一个最典型的场景就是:我们在遍历map中元素的同时不能对元素进行remove操作。这是因为我们遍历map中元素的开始时,jdk帮我们创建了一个迭代器,并记录了hashMap中modCount的值,如果遍历的过程中某些元素被删除或者添加一个元素会影响遍历的结果甚至因为元素被删除抛出异常,所以引入modCount变量防止其他因素对当前操作的影响。

以上5点是我在学习中总结的JAVA基础知识,小高会持续输出自己学到的Java技术干货,记录生活中的成长感悟。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值