Java的HashMap源码解析

 更新时间:2023年11月15日 10:11:55   作者:龙三丶  
这篇文章主要介绍了Java的HashMap源码解析,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对是一个Node,后台是用一个Node数组来存放数据,这个Node数组就是HashMap的主干,需要的朋友可以参考下

前言

以jdk1.8为例,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对是一个Node(jdk1.7叫做Entry)。后台是用一个Node数组来存放数据,这个Node数组就是HashMap的主干。

这里我们主要来分析HashMap的get和put方法。

put

public V put(K key, V value) {
	    	return putVal(hash(key), key, value, false, true);
	}
 
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
				   boolean evict) {
		Node<K,V>[] tab; Node<K,V> p; int n, i;
		//如果是第一次put,就进行数组的大小初始化,默认是16
		if ((tab = table) == null || (n = tab.length) == 0)
			n = (tab = resize()).length;
		//根据hash值,找到在数组中的位置,如果此位置没有值,就new一个新的node插入
		if ((p = tab[i = (n - 1) & hash]) == null)
			tab[i] = newNode(hash, key, value, null);
		//如果数组该位置有值
		else {
			Node<K,V> e; K k;
			//判断该位置节点的key是否和即将插入的key相等,相等就取出来等待覆盖
			if (p.hash == hash &&
					((k = p.key) == key || (key != null && key.equals(k))))
				e = p;
			//若果该节点是红黑树,则调用红黑树相关方法
			else if (p instanceof TreeNode)
				e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
			//到了这里,说明该节点是链表,调用链表相关方法
			else {
				for (int binCount = 0; ; ++binCount) {
					//循环到最后一个节点,然后插入新节点(1.7是往头结点插入,1.8是往尾部插入)
					if ((e = p.next) == null) {
						p.next = newNode(hash, key, value, null);
						//判断插入后的该链表的长度,如果大于8,就转成红黑树
						if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							treeifyBin(tab, hash);
						break;
					}
					//这里表示链表中某个节点的key与即将插入的key相等,就跳出循环等待覆盖
					if (e.hash == hash &&
							((k = e.key) == key || (key != null && key.equals(k))))
						break;
					p = e;
				}
			}
			//这里表示有节点的key与新的key相等,那么就覆盖
			if (e != null) {
				V oldValue = e.value;
				if (!onlyIfAbsent || oldValue == null)
					e.value = value;
				afterNodeAccess(e);
				return oldValue;
			}
		}
		++modCount;
		//插入完之后,如果导致size超过了预设的阈值,就进行扩容(1.7是插入前判断,1.8是插入后判断)
		if (++size > threshold)
			resize();
		afterNodeInsertion(evict);
		return null;
	}

扩容步骤:

1、创建一个原数组两倍大小的新数组,并且把阈值扩大一倍。

2、遍历原数组,进行数据迁移。分为红黑树和链表两种情况。

 好了,扩容部分就不展开代码详细说明,接下来进入get方法,相较于put方法就没那么复杂了,且代码量也比较少

get

public V get(Object key) {
		Node<K,V> e;
		return (e = getNode(hash(key), key)) == null ? null : e.value;
	}
	final Node<K,V> getNode(int hash, Object key) {
		Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
		//判断底层node数组是否为空及该hash值对应的数组位置是否有值
		if ((tab = table) != null && (n = tab.length) > 0 &&
				(first = tab[(n - 1) & hash]) != null) {
			//判断该数组的节点是不是我们需要的值,是就直接返回
			if (first.hash == hash &&
					((k = first.key) == key || (key != null && key.equals(k))))
				return first;
			if ((e = first.next) != null) {
				//该节点是红黑树则直接调用红黑树的遍历方法
				if (first instanceof TreeNode)
					return ((TreeNode<K,V>)first).getTreeNode(hash, key);
				//遍历链表
				do {
					if (e.hash == hash &&
							((k = e.key) == key || (key != null && key.equals(k))))
						//是我们需要的值,返回
						return e;
				} while ((e = e.next) != null);
			}
		}
		return null;
	}

注意:

1、HashMap底层就是用一个个的Node来存储单个数据,每个Node有hash值、key、value、及指向下一个Node的引用(next)。Node数组中就是所有链表的头节点。

2、当出现hash冲突的情况,原Node的next就会指向新插入的Node,也就是形成了链表。

3、每次扩容的长度必须是2的幂,因为,根据key的hash值计算出的数组索引应尽量不要重复,实现均匀分布,均匀分布的话大部分查找的数据都是以数组的形式查找,就不会蜕变成链表,而数组的查找效率比链表高很多。

4、影响扩容的因素有两个:数组的长度(DEFAULT_INITIAL_CAPACITY)和负载因子(DEFAULT_LOAD_FACTOR),当这两个相乘大于等于当前HashMap的Size时,就进行扩容

5、扩容在并发情况下可能会形成链表环,存在并发安全问题,这点需要注意

6、当链表的节点超过8个时,会转成红黑树,链表的时间复杂度为O(n),而红黑树为O(logn)

到此这篇关于Java的HashMap源码解析的文章就介绍到这了,更多相关HashMap源码 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SSM框架+Plupload实现分块上传大文件示例

    SSM框架+Plupload实现分块上传大文件示例

    这篇文章主要介绍了SSM框架+Plupload实现分块上传示例(Spring+SpringMVC+MyBatis+Plupload),将用户选中的文件(可多个)分隔成一个个小块,依次向服务器上传,有兴趣的可以了解一下。
    2017-03-03
  • Springboot项目启动成功后可通过五种方式继续执行

    Springboot项目启动成功后可通过五种方式继续执行

    本文主要介绍了Springboot项目启动成功后可通过五种方式继续执行,主要包括CommandLineRunner接口,ApplicationRunner接口,ApplicationListener接口,@PostConstruct注解,InitalizingBean接口,感兴趣的可以了解一下
    2023-12-12
  • Java Eclipse中实现快速替换变量

    Java Eclipse中实现快速替换变量

    这篇文章主要介绍了Java Eclipse中实现快速替换变量,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java http加签、验签实现方案详解

    Java http加签、验签实现方案详解

    这篇文章主要介绍了Java http加签、验签实现方案详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-07-07
  • default怎么修饰接口中的方法详解

    default怎么修饰接口中的方法详解

    今天给各位小伙伴们总结一下default怎么修饰接口中的方法,文中有非常详细的图文解说.对正在学习java的小伙伴们很有帮助,需要的朋友可以参考下
    2021-05-05
  • 解决mybatis中的mapper命名问题

    解决mybatis中的mapper命名问题

    这篇文章主要介绍了解决mybatis中的mapper命名问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 深入理解ContextClassLoader加载器

    深入理解ContextClassLoader加载器

    这篇文章主要介绍了深入理解ContextClassLoader加载器,Thread context class loader存在的目的主要是为了解决parent delegation机制下无法干净的解决的问题,需要的朋友可以参考下
    2023-10-10
  • SpringBoot配置RocketMQ的详细过程

    SpringBoot配置RocketMQ的详细过程

    这篇文章主要介绍了SpringBoot配置RocketMQ的详细过程,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • SpringBoot热部署设置方法详解

    SpringBoot热部署设置方法详解

    在实际开发中,每次修改代码就需要重启项目,重新部署,对于一个后端开发者来说,重启确实很难受。在java开发领域,热部署一直是一个难以解决的问题,目前java虚拟机只能实现方法体的热部署,对于整个类的结构修改,仍然需要重启项目
    2022-10-10
  • java使用OpenCV从视频文件中获取帧

    java使用OpenCV从视频文件中获取帧

    这篇文章主要为大家详细介绍了java使用OpenCV从视频文件中获取帧,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07

最新评论