浅析HashMap底层原理

本文详细解析了HashMap的底层数据结构(数组+链表/红黑树),包括哈希冲突的处理方式、添加和查找元素的过程以及1.7和1.8版本的区别,重点介绍了put和get方法的工作机制。
摘要由CSDN通过智能技术生成

HashMap底层源码解析

1.底层数据结构:哈希表

我们常见的数据结构有三种:

  • 数组结构
  • 链表结构
  • 哈希表结构

HashMap的底层数据结构是哈希表:

  1. 数组+链表   同一位置下,节点数<=8;

  2. 数组+红黑树   同一个位置下,节点的个数>8 并且 数组长度大于64;

 补充(hash冲突):

  • 当两个key通过hashCod计算相同时(其实hashCode是随机产生的,是有可能hashCode相同,因为哈希表是有限的,但存入的东西可能很多难免有些会有相同的hashCode,从而发生冲突。例如:"通话"和"重地"),则发生了hash冲突
  • HashMap解决hash冲突的方式是用链表。当发生hash冲突时,则将存放在数组中的Entry设置为新值的next,举例来说,比如A和B都hash后都映射到下标i中,之前已经有A了,当map.put(B)时,将B放到下标i中,A则为B的next,所以新值存放在数组中,旧值在新值的链表上
  • 主要的解决冲突的方法有:开放定址法、再哈希法、链地址法、建立公共溢出区

 2.HashMap中添加元素和查找元素

HashMap中添加元素方法为map.put(k,v);查找元素的方法为map.get(k);

情景实例:(tips:可以按住Ctrl+鼠标点击方法名的方式进入方法体里)

package Test;

import java.util.HashMap;

public class Demo02 {
	public static void main(String[] args) {
		//假设一个情景,使用hashmap集合录入一批人姓名及其所具有的宝石数
		HashMap<String, Integer> hashMap=new HashMap<String, Integer>();
		
		hashMap.put("张三", 6);
		hashMap.put("李四", 7);
		hashMap.put("王五", 8);
		
		int m=hashMap.get("李四");
		System.out.println("李四所具有的宝石数为:"+m);
		System.out.println("hashMap集合:"+hashMap);
	}
}

 

2.1、源码中的map.put(k,v)方法

2.1.1、求元素的哈希值

 

 从map.put()方法进入后可以看到源码中的put()方法,传入了键key和值value.在进入下一个putVal()方法前先执行了hash()方法,判断了传入键的哈希值;进入hash()方法可以看到一个三元运算符,其表达的意思是若传进来的key为null返回0,否则对其进行hashCode(),拿到对应的哈希值然后和0进行异或运算。

补充:

  • h>>>16:如果元素(key)的哈希值是在65535的范围内,那么该结果都为0
  • 任何数异或(^)0,还是原来的数
2.1.2、添加元素(map集合为空时)

 进入putVal这个添加元素的主要方法中后,可以看到会进行创建一个tab数组

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; 

进入第一个if判断时,若map集合中没有元素,会进入resize()方法。

 进入resize()方法后可以观察到, int oldCap = (oldTab == null) ? 0 : oldTab.length;

 

 这样就创建了一个容量为16的map集合,可以将元素添加到map集合中。

2.1.3、确定元素位置(在数组中那个索引位置)

总结:新增的对应位置(索引值): 哈希值%16

 2.1.4、新增元素

 

若该索引位置有节点 但hash值不同,获取头结点的下一个节点,判断是否为空:

        for(;;){

                如果为空:直接把该节点挂到头结点的后面

                如果不为空:下一个节点和要添加的元素进行操作。

                        p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))):

                        如果该结果为true,则不新增。

                        否则:继续上面的循环一次走。

        }

 

 2.1.5、如何扩容

 

 

2.1.6、扩容后哈希表中元素变化 

 

 

 2.1.7、小结

map.put(k,v)实现原理:

  1. 首先将k,v封装到Node对象当中(节点)
  2. 然后它的底层会调用K的hashCode()方法得出hash值
  3. 通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

2.2 源码中的map.get(k)方法

2.2.1、源码

 

2.2.2、小结

 map.get(k)实现原理:

  1. 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标

  2. 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

3.HashMap的原理1.7和1.8的区别 

区别:

  1. jdk1.7中底层是由数组+链表实现;
  2. jdk1.8中底层是由数组+链表/红黑树实现

优缺点:

  1. 可以存储null键和null值,线程不安全
  2. 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  3. 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
  4. 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀

 tips:你的点赞+关注是小编更新的动力!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值