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源码 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot配置使Mybatis打印SQL执行时的实际参数值操作

    SpringBoot配置使Mybatis打印SQL执行时的实际参数值操作

    这篇文章主要介绍了SpringBoot配置使Mybatis打印SQL执行时的实际参数值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • SpringBoot中使用HTTP客户端工具Retrofit

    SpringBoot中使用HTTP客户端工具Retrofit

    这篇文章主要为大家介绍了SpringBoot中使用HTTP客户端工具Retrofit方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 详解context root修改无效web修改项目路径(eclipse)

    详解context root修改无效web修改项目路径(eclipse)

    这篇文章主要介绍了详解context root修改无效web修改项目路径(eclipse)的相关资料,需要的朋友可以参考下
    2017-04-04
  • 解决mybatis case when 报错的问题

    解决mybatis case when 报错的问题

    这篇文章主要介绍了解决mybatis case when 报错的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • spring boot如何基于JWT实现单点登录详解

    spring boot如何基于JWT实现单点登录详解

    这篇文章主要介绍了spring boot如何基于JWT实现单点登录详解,用户只需登录一次就能够在这两个系统中进行操作。很明显这就是单点登录(Single Sign-On)达到的效果,需要的朋友可以参考下
    2019-06-06
  • Java和c语言随机数Random代码详细

    Java和c语言随机数Random代码详细

    这篇文章主要介绍Java和c语言得随机数Random,随机数的用处在生活中比较少见,但是用处并不少,比如一些小游戏的制作等等。下面我们就一起来学习这篇关于Java和c随机数Random得文章吧
    2021-10-10
  • Java汉字转成汉语拼音工具类

    Java汉字转成汉语拼音工具类

    这篇文章主要为大家详细介绍了Java汉字转成汉语拼音工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • SpringMVC中常用注解与使用方法详解

    SpringMVC中常用注解与使用方法详解

    这篇文章主要介绍了SpringMVC中常用注解与使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • Java创建List常用几种方法

    Java创建List常用几种方法

    本文主要介绍了Java创建List常用几种方法,主要介绍了9种方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Java创建删除文件和目录的方法(推荐)

    Java创建删除文件和目录的方法(推荐)

    这篇文章主要介绍了java创建删除文件和目录的方法,创建和删除文件目录常用的是file类的方法,具体内容详情大家参考下本文
    2018-05-05

最新评论