为什么HashMap的键值可以为null,而ConcurrentHashMap不行?
写在开头
昨天在写《HashMap很美好,但线程不安全怎么办?ConcurrentHashMap告诉你答案!》这篇文章的时候,漏了一个知识点,知道晚上吃饭的时候才凸显想到,关于ConcurrentHashMap在存储Key与Value的时候,是否可以存null的问题,按理说这是一个小问题,但build哥却不敢忽视,尤其在现在很多面试官都极具挑剔的环境下,万一同学们刷到了咱的博客,回答中遗漏了这个小细节,错过了面试官的考验,那咱可就成罪人了。
接下来我们就将HashMap、Hashtable、ConcurrentHashMap这三集合类的键值是否可以null的问题,放一起对比去学习一下。
Hashtable的键值与null
虽然我们在讲解HashMap与Hashtable作对比时,已经说了Hashtable在存储key与value时均不可为null,但当时的侧重点全在HashMap身上,就没有详细的解释原因,下面我们跟进put源码中去一探缘由。
【源码解析1】
public synchronized V put(K key, V value) {
// 确认值不为空
if (value == null) {
throw new NullPointerException(); // 如果值为null,则抛出空指针异常
}
// 确认值之前不存在Hashtable里
Entry<?,?> tab[] = table;
int hash = key.hashCode(); // 如果key如果为null,调用这个方法会抛出空指针异常
int index = (hash & 0x7FFFFFFF) % tab.length;//计算存储位置
//遍历,看是否键或值对是否已经存在,如果已经存在返回旧值
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
通过Hashtable的put底层源码,我们可以看到,方法体内,首先就对value值进行的判空操作,如果为空则抛出空指针异常;其次在计算hash值的时候,直接调用key的hashCode()方法,若keynull,自然也会报空指针异常,因此,我们在调用put方法存储键值对时,key与value都非null。
HashMap的键值与null
我们同样也通过HashMap的put方法去分析它的底层源码,先上代码。
【源码解析2-hash()】
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在计算hash值的时候,hashmap中通过三目运算符做了空值处理,直接返回0,这样最终计算出key应该存储在数组的第一位上,且key是唯一性呢,因此,key最多存一个null;
【源码解析3】
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
// 数组
HashMap.Node<K,V>[] tab;
// 元素
HashMap.Node<K,V> p;
// n 为数组的长度 i 为下标
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);
///
}
回归putVal()方法,我们逐句阅读后也没有发现对于value值为null的处理与限定,因此,它可以存储为null的value值,我们知道HashMap的键值对特点如同身份证与人名一样,key等同于身份证,全国唯一,而value值等同于人名,可以重复,比如全国有上万个叫张伟的,所以value值也就同样允许存储多个null。
ConcurrentHashMap的键值与null
很多同学们可能会以为ConcurrentHashMap不过是HashMap在多线程环境下的版本,底层实现都一致,只是多了加锁的操作,所以二者对于null的允许程度是一样。
如果你是这样想,那可就完全错了,对于ConcurrentHashMap来说,它也不允许存储键值对为null的数据。
Doug Lea(ConcurrentHashMap的设计者)曾这样说道:
The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.
大致的意思是,在单线程环境中,不会存在一个线程操作该 HashMap 时,其他的线程将该 HashMap 修改的情况,可以通过 contains(key)来做判断是否存在这个键值对,从而做相应的处理;
而在多线程环境下,可能会存在多个线程同时修改键值对的情况,这时是无法通过contains(key)来判断键值对是否存在的,这会带来一个二义性的问题,Doug Lea说二义性是多线程中不能容忍的!
啥是二义性? 咱们通俗点讲就是一个结果,2种释义,就好比我们通过get方法获取值的时候,返回一个null,其实我们是无法判断是值本身为null还是说集合中就没这个值!
所以说,ConcurrentHashMap的key和value均不可为null。
结尾彩蛋
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注俺滴公众号“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

为什么HashMap的键值可以为null,而ConcurrentHashMap不行?的更多相关文章
- HashTable、ConcurrentHashMap、TreeMap、HashMap关于键值的区别
集合类 key value super 说明 HashTable 不能为null 不能为null Dictionary 线程安全 ConcurrentHashMap 不能为null 不能为null A ...
- hashmap可以用null为键值
import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class TestMain { ...
- Android4.2增加新键值
这里添加新的键值,不是毫无凭据凭空创造的一个键值, 而是根据kernel中检测到的按键值,然后转化为android所需要的数值: 以添加一个linux键值为217,把它映射为android的键值Bro ...
- C语言定义从URL中获取键值的接口
环境:centos7下,对客户端http请求进行解析,来获取有效键值(包括汉字). 头文件 /* 这是一份关于从Http请求信息中提取键值的接口声明的头文件 */ #ifndef _HEAD_H_ # ...
- 【转】HashMap、TreeMap、Hashtable、HashSet和ConcurrentHashMap区别
转自:http://blog.csdn.net/paincupid/article/details/47746341 一.HashMap和TreeMap区别 1.HashMap是基于散列表实现的,时间 ...
- HashMap允许将null用作键 也允许将null作为值
HashMap不能保证元素的顺序,HashMap能够将键设为null,也可以将值设为null. 与之对应的是Hashtable,(注意大小写:不是HashTable),Hashtable不能将键和值设 ...
- JAVA中的数据结构——集合类(线性表:Vector、Stack、LinkedList、set接口;键值对:Hashtable、Map接口<HashMap类、TreeMap类>)
Java的集合可以分为两种,第一种是以数组为代表的线性表,基类是Collection:第二种是以Hashtable为代表的键值对. ... 线性表,基类是Collection: 数组类: person ...
- 从源码看HashMap键值对集合
之前我们看过了两种类型的集合,ArrayList集合和LinkedList集合,两种集合各有优势,我们不具体说了,但是本篇要看的集合可以完成它们完成不了的任务.比如:现有一篇文章,要你统计其中出现了哪 ...
- [19/03/26-星期二] 容器_Map(图、键值对、映射)接口之HashMap(散列映射)&TreeMap(树映射)
一.概念&方法 现实生活中,我们经常需要成对存储某些信息.比如,我们使用的微信,一个手机号只能对应一个微信账户,这就是一种成对存储的关系. Map就是用来存储“键(key)-值(value) ...
- HashMap存储自定义类型键值和LinkedHashMap集合
HashMap存储自定义类型键值 1.当给HashMap中存放自定义对象时,如果自定义对象是键存在,保证键唯一,必须复写对象的hashCode和equals方法. 2.如果要保证map中存放的key和 ...
随机推荐
- 【杂题,树】【Uoj】Uoj618 【JOISC2021】聚会 2
2023.7.3 Problem Link 给定一棵 \(n\) 个点的树,对于一个点集 \(S\),定义 \(f(u,S)\) 为 \(\min_u \sum_{v\in S} \mathrm{di ...
- React中useEffect、useCallBack、useLayoutEffect
在函数中使用定时器 import { useEffect, useState } from "react"; export default function Funcom() { ...
- docker的架构及工作原理(详解)
目录 一.docker架构图 二.Client 客户端 三.Host 主机(docker引擎) 四.Image 镜像 五.Container 容器 六.镜像分层 可写的容器层 七.Volume 数据卷 ...
- go中context源码解读
context 前言 什么是context 为什么需要context呢 context底层设计 context的实现 几种context emptyCtx cancelCtx timerCtx val ...
- 什么是 Java 字节码?采用字节码的好处是什么?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机.Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效 ...
- Flask 框架:运用SocketIO实现WebSSH
Flask 框架中如果想要实现WebSocket功能有许多种方式,运用SocketIO库来实现无疑是最简单的一种方式,Flask中封装了一个flask_socketio库该库可以直接通过pip仓库安装 ...
- 从嘉手札<2023-11-13>
1. 很多时候 成功并不等同于成长 成功是很多因素复合形成的一种结果 而并不等同于一个人阅历的丰富.认知的提高 2. 我一直认为 世界不属于投机者 也不属于堕落者 信念感在这个大数据泛滥.碎片化汹涌的 ...
- 守护进程(Python)
#__author__:Kelvin #date:2020/5/10 11:37 import time from multiprocessing import Process def son1(): ...
- P5309 [Ynoi2011] 初始化 题解
题目链接:初始化 这种 ynoi 的老题就是卡常.来简单说说这题的思维切入口. 看到形如 \(y+k \times x\) 的结构,自然而然思考一下如果我们是暴力更新会有怎么样的效果.我们容易发现,如 ...
- 面试官:Sentinel是如何实现限流的?
限流是一种通过控制系统对外提供的资源.服务或接口的访问数量或速率,以保护系统免受过载的一种策略. 它的目的是确保系统能够在承受范围内提供稳定和可靠的服务,避免因过多的请求而导致系统崩溃.资源耗尽或响应 ...