hashMap底层原理【仅做记录学习使用】

本文转载自Java HashMap底层原理【记录】_java hashmap底层实现原理-CSDN博客

HashMap是基于哈希表的Map接口的非同步实现。实现HashMap对数据的操作,允许有一个null键,多个null值。

JDK1.7:HashMap底层是一个数组+链表结构,创建HashMap的时候就会分配一个数组空间(大小为16);

JDK1.8+:HashMap增加了红黑树(数组+链表+红黑树),当数组的元素大于等于64并且链表元素个数大于等于8时,链表就换转为红黑树,创建HashMap时不会去分配空间,只有put第一个元素的时候才会去分配空间。

  • 横向被称作table[] 数组
  • 纵向被称作bucket哈希桶,实际上就是由Entry组成的链表。

新建一个HashMap的时候,就会初始化一个Entry数组,每个Entry其实就是一个key-value的键值对,HashMap底层将key-value当成一个整体来处理,这个整体就是一个Entry对象。当转成链表时,每个Entry对象中都存储着下个元素的地址信息。

当需要存储一个Entry对象时,会根据hash算法来决定在其数组(散列桶)中的位置,如果该位置有元素,再根据equals方法判断是否是同一对象,如果是则替换其value值,如果不是则拼接到链表尾部,当需要取出一个Entry对象时,也会根据hash算法找到其在数组中的存储位置,在根据equals方法从该位置上的链表中取出Entry:

HashMap的存储:

put:(key-value)方法是HashMap中最重要的方法,使用HashMap主要使用的就是put,get两个方法。

1、判断键值对数组table是否为空,如果为空则执行resize()进行扩容。

2、根据键值key计算hash值得到插入的数组索引 i ,如果table[i] == null,直接新建节点添加即可,转入6,如果table[i]不为空,则转向3。

3、通过equals判断table[i]位置的元素是否和key一样,如果相同直接覆盖其value,否则转向4。

4、判断table[i]是否为treeNode(红黑树),如果是红黑树,则直接插入键值对,否则转向5。

5、遍历table[i]判断链表长度是否大于等于8,如果大于等于8并且hashMap数组容量大于等于64的话把链表转为红黑树,进行插入操作,否则进行链表插入操作。

6、插入成功后,判断实际存在的键值对数量size是否超过了threshold,如果超过,则扩容。

看一下put原码:

get方法取值过程:

int hash = key.hashCode();

int index = hash%Entry[].length;

1、指定key通过hash函数得到key的hash值。

2、调用内部方法getNode(),得到桶号(一般为hash值对桶数求模)。

3、比较桶内的内部元素是否和key相等,如不相等,则没有找到,相等,则取出相等记录的value。

4、如果得到key所在桶的头结点恰好是红黑树节点,就调用红黑树节点的getTreeNode()方法,否则就遍历链表节点。getTreeNode()方法通过调用红黑树的find()方法进行查找,由于之前添加时已经保证这个树是有序的,因此查找时基本就是折半查找,效率高。

5、如果对比节点的哈希值和要查找的哈希值相等,就会判断key是否相等,如果桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值的时候,许多查询就会更快。

addEntry方法:

1、添加新元素前,判断是否需要对map进行扩容,如果需要扩容,那扩容多大?

2、对于新增key-value键值对,如果可以的hash值相同,则构造单向列表。

原码分析:

createEntry:

该方法主要完成两个功能,一个是添加新的key到Entry数组中,第二个就是对于不同的key的hash值相同的情况下,在同一个数组下标出,构建单向链表进行存储。

原码如下:


HashMap碰撞以及解决方法(开放定址法,在哈希法,链地址法,建立一个公共溢出区)

当两个对象的hashCode相同时,他们的bucket位置相同,hash碰撞就会发生。因为HashMap使用LinkedList存储对象,这个Entry(存储键值对的Map.Entry对象)会存储在LinkedList中。这两个对象就算hashCode相同,但是他们可能并不相等。那么如何获取这两个对象的值呢?当我们调用get()方法,HashMap会使用键值对象的hashCode找到bucket位置,遍历LinkedList一直找到值对象。找到bucket位置以后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值的对象,使用final修饰,并采用合适的equals和hashCode()方法,减少碰撞。

HashMap扩容机制:

扩容必须满足两个条件

存放新值的时候当前你已有元素必须大于阈值。
存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值算出来的数组索引位置已经存在值)
1、HashMap在添加值的时候,它默认能存储16个键值对,直到你使用这个HashMap时,它才会给HashMap分配16个键值对的存储空间,(负载因子为0.75,阈值为12),当16个键值对已经存储满了,我们在添加第17个键值对的时候才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。

2、HashMap也有可能存储更多的键值对,最多可以存储26个键值对,我们来算一下:存储的前11个值全部发生hash碰撞,存到数组的同一个位置中,(这时元素个数小于阈值12,不会扩容),之后存入15个值全部分散到数组剩下的15个位置中,(这时元素个数大于等于阈值,但是每次存入元素并没有发生hash碰撞,不会扩容),11+15=26,当我们存入第27个值的时候满足以上两个条件,HashMap才会发生扩容。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值