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

相关文章

GO语言zap日志库理解和使用方法示例

《GO语言zap日志库理解和使用方法示例》Zap是一个高性能、结构化日志库,专为Go语言设计,它由Uber开源,并且在Go社区中非常受欢迎,:本文主要介绍GO语言zap日志库理解和使用方法的相关资... 目录1. zap日志库介绍2.安装zap库3.配置日志记录器3.1 Logger3.2 Sugared

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

深入理解MySQL流模式

《深入理解MySQL流模式》MySQL的Binlog流模式是一种实时读取二进制日志的技术,允许下游系统几乎无延迟地获取数据库变更事件,适用于需要极低延迟复制的场景,感兴趣的可以了解一下... 目录核心概念一句话总结1. 背景知识:什么是 Binlog?2. 传统方式 vs. 流模式传统文件方式 (非流式)流

深入理解Go之==的使用

《深入理解Go之==的使用》本文主要介绍了深入理解Go之==的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录概述类型基本类型复合类型引用类型接口类型使用type定义的类型不可比较性谈谈map总结概述相信==判等操作,大

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

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的工