HashMap集合中的put()和putIfAbsent()的理解

2024-05-07 08:32

本文主要是介绍HashMap集合中的put()和putIfAbsent()的理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序言

关于Map集合大家都不陌生,最常用的无非就是HashMap,TreeMap,大家都知道,Map是用于键值对key->value的形式来保存数据的,所以我们最熟悉的方法莫过于map的put方法,map的put方法有好几个,可能会有一些人会搞不懂,我就把自己的对这些put方法的理解记录一下,如果有哪里不对的还请各位大神帮忙提出来,在这我先谢谢各位了,不喜欢,请不要骂我,毕竟我也是自己学习的,下面开始就带大家一起来了解一下,这些put方法都是干啥的。

方法

  • put方法

     V put(K key, V value)
    
  • putIfAbsent方法

     V putIfAbsent(K key, V value)
    

概述

首先,HashMap基于Map接口实现,元素以键值对的方式存储并且允许使用null 建和null,但是Map的Key不允许重复,所以只能有一个Key为null,这两个方法其实都是像map中添加数据,那有朋友可能会说了,我们都有put方法了,为什么还要有putIfAbsent方法呢,这不是多此一举么?其实我们再想想,怎么会无缘无故新增加几个方法呢,肯定是有目的的,那么我们来深入理解一下他们两个之间的区别,首先我们知道Map底层的实现是通过采用数组+链表+红黑树实现来实现的,HashMap在之前的时候是数据结构的存储由数组+链表的方式,后来在jdk1.8中变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树(图是借鉴了网上的,大家不要介意):
在这里插入图片描述

源码

看了上面的数据结构,应该能明白Map是怎么来保存数据的了,可能刚开始我自己还没有弄清楚,如果能明白上面的存储方式,我们再来看一下它的源码,这样就能加快我们的理解,不明白上面的数据结构也不要紧,大家可以自己查查资料,我们来看下他们两个的源码,然后在进行下一步分析:

  • put():
@Override
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}
  • putIfAbsent
@Override
public V putIfAbsent(K key, V value) {return putVal(hash(key), key, value, true, true);
}

目前这么一看,两个方法都调用了putval(),好像没啥区别,等等,貌似第四个参数不一样,恩,我们看一下putval()这个方法都做了些什么?

/*** Implements Map.put and related methods** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;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) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;//如果onlyIfAbsent为false并且oldValue为null,我们便对我们的value进行保存if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

你们看到了什么,put()putIfAbsent() 都是通过这个方法实现了数据的存储,我们来看下,putval() 的第四个参数做了什么样的判断:

if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;
}

我们可以看到putval() 的第四个参数在这段代码里面的第二个if语句里面做了判断,如果onlyIfAbsentfalse并且oldValuenull的情况下,就对我们当前传进来的value进行保存,这是为什么呢?上面这段代码的e.value 其实就是Node的value,可能大家觉得这块不太好理解,我们再来看下这两个方法运行的结果,来进行辅助理解。
###代码示例

public class Main {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("1", "1");System.out.println(map.putIfAbsent("1", "2"));System.out.println(map.get("1"));System.out.println(map.put("1", "3"));System.out.println(map.get("1"));}
}

这段代码很简单,我们在map里面保存了一组数据,1->1,然后我们在用System.out.println 分别输出一下put和putIfAbsent之后的key=1所对应的value的值,我们看下运行结果:

代码运行结果

我们看到运行结果,是不是我们所期望的结果呢?首先putIfAbsent() 并没有将value的1替换掉为2,还是为1,但是我们用put() 方法却把value的1替换为3了,说明什么,说明我们如果用putIfAbsent()保存数据的时候,如果该链表中保存的有相同key的值,那么就不会对我们当前的value进行保存,如果用put()存储数据的时候,不管是该链表中是否有当前需要存储的key都会保存,我们所要保存的当前key所对应的value,看注释就很明白了。
也会有朋友说,那既然这样,还不如put方法来得快呢,我们来看下,官方的解释:

     /*** If the specified key is not already associated with a value (or is mapped* to {@code null}) associates it with the given value and returns* {@code null}, else returns the current value.** @implSpec* The default implementation is equivalent to, for this {@code* map}:** <pre> {@code* V v = map.get(key);* if (v == null)*     v = map.put(key, value);** return v;* }</pre>** <p>The default implementation makes no guarantees about synchronization* or atomicity properties of this method. Any implementation providing* atomicity guarantees must override this method and document its* concurrency properties.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with the specified key, or*         {@code null} if there was no mapping for the key.*         (A {@code null} return can also indicate that the map*         previously associated {@code null} with the key,*         if the implementation supports null values.)* @throws UnsupportedOperationException if the {@code put} operation*         is not supported by this map*         (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)* @throws ClassCastException if the key or value is of an inappropriate*         type for this map*         (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)* @throws NullPointerException if the specified key or value is null,*         and this map does not permit null keys or values*         (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)* @throws IllegalArgumentException if some property of the specified key*         or value prevents it from being stored in this map*         (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)* @since 1.8*/default V putIfAbsent(K key, V value) {V v = get(key);if (v == null) {v = put(key, value);}return v;}

这段代码是map里面关于putIfAbsent() 方法的定义,官方的解释就是:
If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.
如果指定的键未与某个值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
官方还有这样一句话:默认的实现对该方法的同步或原子性质没有保证。提供原子性的保证任何的实现必须重写此方法和文件的并发性能。
这里说的很清楚,如果要实现并发,则必须重写该方法,这样的话,其用法就很明白了。
###结语
好了,今天的介绍就到这里,我想这两个方法,大家都有所了解了,他们的作用也了然于心了,有什么不懂得可以留言讨论,我会及时给大家解答。

这篇文章之前有一些错误,我在这给大家道歉,文章主要帮助大家理解文中的两个方法,没有详细介绍HashMap,只是所以还请大家谅解,两次感谢大家,??

这篇关于HashMap集合中的put()和putIfAbsent()的理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966843

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

python语言中的常用容器(集合)示例详解

《python语言中的常用容器(集合)示例详解》Python集合是一种无序且不重复的数据容器,它可以存储任意类型的对象,包括数字、字符串、元组等,下面:本文主要介绍python语言中常用容器(集合... 目录1.核心内置容器1. 列表2. 元组3. 集合4. 冻结集合5. 字典2.collections模块

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

Java中HashMap的用法详细介绍

《Java中HashMap的用法详细介绍》JavaHashMap是一种高效的数据结构,用于存储键值对,它是基于哈希表实现的,提供快速的插入、删除和查找操作,:本文主要介绍Java中HashMap... 目录一.HashMap1.基本概念2.底层数据结构:3.HashCode和equals方法为什么重写Has

Java集合中的链表与结构详解

《Java集合中的链表与结构详解》链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序的通过链表中的引用链接次序实现,文章对比ArrayList与LinkedList的结构差异,详细讲解了链表... 目录一、链表概念与结构二、当向单链表的实现2.1 准备工作2.2 初始化链表2.3 打印数据、链表长