高德面试:为什么Map不能插入null?

map,null · 浏览次数 : 14

小编点评

在 Java 中,Map 是 java.util 包下的一种接口,它允许使用 key 和 value。HashMap 是支持 null 的,而 ConcurrentHashMap 则不允许。 1. HashMap 和 ConcurrentHashMap 的区别: - HashMap 允许 key 和 value 值都为 null。 - ConcurrentHashMap 不允许 key 和 value 值都为 null。 - ConcurrentHashMap 的 key 和 value 都不能为 null。 2. 为什么不能插入 null? - 由于 ConcurrencyHashMap 的实现是基于线程安全的考虑,所以不允许 key 或 value 为 null,以避免出现“二义性问题”。 - 在多线程环境下,null 的二义性问题无法被证明真伪,因此 ConcurrentHashMap 选择禁用 null 值。 3. 深层次原因: - ConcurrentHashMap 是为了并发环境设计的,而多线程环境下的二义性问题难以被证明。 - 避免 null 值可以避免在并发环境下出现不确定的结果。 4. 其他不需要使用 null 作为 key 或 value 的容器: - Redis - JVM - MySQL - Spring - Spring MVC - Spring Boot - Spring Cloud - MyBatis - 设计模式 - 消息队列等。

正文

在 Java 中,Map 是属于 java.util 包下的一个接口(interface),所以说“为什么 Map 不能插入 null?”这个问题本身问的不严谨。Map 部分类关系图如下:
image.png
所以,这里面试官其实想问的是:为什么 ConcurrentHashMap 不能插入 null?

1.HashMap和ConcurrentHashMap的区别

HashMap 和 ConcurrentHashMap 在对待 null 的态度上是不同的,在 Java 中,HashMap 是允许 key 和 value 值都为 null 的,如下代码所示:

HashMap<String, Object> map = new HashMap();
map.put(null, null);
if (map.containsKey(null)) {
    System.out.println("存在 null");
} else {
    System.out.println("不存在 null");
}

以上程序的执行结果如下:

存在 null

从上述结果可以看出,HashMap 是允许 key 和 value 值都为 null 的。

但 ConcurrentHashMap 就不同了,它不但 key 不能为 null,而且 value 也不能为 null,如以下代码所示:

ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put(null, "javacn.site");
System.out.println(concurrentHashMap.get(null));

在运行以上程序时就会报错,如下图所示:
image.png
当然,当你为 ConcurrentHashMap 的 value 值设置 null 时也会报错,如下代码所示:

String key = "www.Javacn.site";
ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put(key, null);
System.out.println(concurrentHashMap.get(key));

在运行以上程序时就会报错,如下图所示:
image.png
因此,我们可以得出结论:

  1. 在 HashMap 中,key 和 value 值都可以为 null。
  2. 在 ConcurrentHashMap 中,key 或者是 value 值都不能为 null。

2.为什么不能插入null?

如果我们查看 ConcurrentHashMap 的源码,就能发现为什么 ConcurrentHashMap 不能插入 null 了,以下是 ConcurrentHashMap 添加元素时的部分核心源码:

// 添加 key 和 value
public V put(K key, V value) {
    return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 如果 key 或 value 为 null 的话直接抛出空指针异常
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    // 忽略其他代码......
}

从上述 ConcurrentHashMap 添加元素的第一行源码就可以看出,当 key 或 value 为 null 时,会直接抛出空指针异常,这就是 ConcurrentHashMap 之所以不能插入 null 的根本原因了,因为源码就是这样设计的。

3.更深层次的原因

那么问题来了,为什么 ConcurrentHashMap 的实现源码中,不允许为 key 或者是 value 设置 null 呢?

这就要从 ConcurrentHashMap 的使用场景说起了,在 Java 中,ConcurrentHashMap 是用于并发环境中执行的线程安全的容器,而 HashMap 是用于单线程环境下执行的非线程安全的容器,而并发环境下的运行更复杂,如果我们允许 ConcurrentHashMap 的 key 或者是 value 为 null 的情况下,就会存在经典的“二义性问题”

3.1 什么是二义性问题?

所谓的二义性问题指的是代码或表达式存在多种理解或解释,导致程序的含义不明确或模糊。

以 ConcurrentHashMap 不允许为 null 的二义性问题来说,null 其实有以下两层含义:

  1. 这个值本身设置的是 null,null 在这里表示的是一种具体的“null”值状态。
  2. null 还表示“没有”的意思,因为没有设置,所以啥也没有。

所以,如果 ConcurrentHashMap 允许插入 null 值,那么就会存在二义性问题。

那就有同学会问了,为什么 HashMap 允许插入 null,它就不怕有二义性问题吗?

3.1 可证伪的HashMap

HashMap 之所以不怕二义性问题的原因是,HashMap 的设计是给单线程使用的,而单线程下的二义性问题是能被证明真伪的,所以也就不存在二义性问题了(能被证明的问题就不是二义性问题)

例如,当我们给 HashMap 的 key 设置为 null 时,我们可以通过 hashMap.containsKey(key) 的方法来区分这个 null 值到底是存入的 null?还是压根不存在的 null?这样二义性问题就得到了解决,所以 HashMap 的二义性问题可被证明真伪,所以就不怕二义性问题,因此也就可以给 key 或者 value 设置 null 了。

3.2 不可证伪的ConcurrentHashMap

而 ConcurrentHashMap 就不一样了,因为 ConcurrentHashMap 是设计在多线程下使用的,而多线程下的二义性问题是不能被证明真伪的,所以二义性问题是真实存在的

因为在你在证明二义性问题的同时,可能会有另一个线程影响你的执行结果,所以它的二义性问题就一直存在。

例如,当 ConcurrentHashMap 未设置 key 为 null 时,会有这样一个场景,当一个线程 A 调用了 concurrentHashMap.containsKey(key),我们期望返回的结果是 false,但在我们调用 concurrentHashMap.containsKey(key) 之后,未返回结果之前,线程 B 又调用了 concurrentHashMap.put(key,null) 存入了 null 值,那么线程 A 最终返回的结果就是 true 了,这个结果和我们之前预想的 false 完全不一样,这就是不能被证伪的二义性问题。

所以说,多线程的执行比较复杂,在多线程下 null 的二义性问题是不能被证明真伪的(因为在一个线程执行验证时,可能会有另一个线程改动结果,造成结果不准确),所以 ConcurrentHashMap 为了避免这个二义性问题,所以就在源码中禁用了 null 值作为 key 或 value。

课后思考

除了 ConcurrentHashMap 之后,还有哪些容器不允许使用 null 作为 key 或者 value 呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

与高德面试:为什么Map不能插入null?相似的内容:

高德面试:为什么Map不能插入null?

在 Java 中,Map 是属于 java.util 包下的一个接口(interface),所以说“为什么 Map 不能插入 null?”这个问题本身问的不严谨。Map 部分类关系图如下: 所以,这里面试官其实想问的是:为什么 ConcurrentHashMap 不能插入 null? 1.HashM

uniapp引入高德地图或腾讯地图以及map在真机中常见问题汇总

以下是必须肯定会用到的官方地址 腾讯地图API开发文档:微信小程序JavaScript SDK | 腾讯位置服务 (qq.com) 高德地图API开发文档:开发 | 高德地图API (amap.com) 高德地图小程序配置指南:入门指南-微信小程序插件|高德地图API (amap.com) 小程序后

NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下伺服电机,华庆军继电器模块)

先上两个通用Modbus帮助类,下面这个是多线程不安全版,在多线程多电机同一端口通信下,可能造成步进电机丢步或者输出口无响应等,还有个多线程安全版,只是基于这个不安全版加上了LOCK,THIS using Modbus.Device; using Sunny.UI; using System; us

PPO-KL散度近端策略优化玩cartpole游戏

其实KL散度在这个游戏里的作用不大,游戏的action比较简单,不像LM里的action是一个很大的向量,可以直接用surr1,最大化surr1,实验测试确实是这样,而且KL的系数不能给太大,否则惩罚力度太大,action model 和ref model产生的action其实分布的差距并不太大 i

PPO近段策略优化玩cartpole游戏

这个难度有些大,有两个policy,一个负责更新策略,另一个负责提供数据,实际这两个policy是一个东西,用policy1跑出一组数据给新的policy2训练,然后policy2跑数据给新的policy3训练,,,,直到policy(N-1)跑数据给新的policyN训练,过程感觉和DQN比较像,

高一下三调模拟赛5.13(附关于二分图匈牙利建边的详细思考)

前言注:本篇为知识性内容,A题附详解关于匈牙利算法求最大独立子集难以理解的建边问题的思考,若有不当之处感谢指出。暂时只写了A篇题解,以供帮助大家理解相关问题,剩余题解会进行补充。 又是小集训的一周,总要伴随着模拟赛... 还是五道题目: A. 攻击装置 B. 循环 C. 漫步 D. 穿越 E. 结队

策略梯度玩 cartpole 游戏,强化学习代替PID算法控制平衡杆

cartpole游戏,车上顶着一个自由摆动的杆子,实现杆子的平衡,杆子每次倒向一端车就开始移动让杆子保持动态直立的状态,策略函数使用一个两层的简单神经网络,输入状态有4个,车位置,车速度,杆角度,杆速度,输出action为左移动或右移动,输入状态发现至少要给3个才能稳定一会儿,给2个完全学不明白,给

deepspeed 训练多机多卡报错 ncclSystemError Last error

最近在搞分布式训练大模型,踩了两个晚上的坑今天终于爬出来了 我们使用 2台 8*H100 遇到过 错误1 10.255.19.85: ncclSystemError: System call (e.g. socket, malloc) or external library call failed

[转帖]高可用高并发系统设计概念学习 二

高可用高并发系统设计概念学习 二 前言一、隔离术线程隔离进程隔离集群隔离机房隔离读写隔离动静隔离爬虫隔离 二、超时与重试机制代理层超时与重试客户端超时设置client_header_timeout timeclient_body_timeout timesend_timeout timekeepal

高可用系列文章之一 - 概述

一 概述 可用性是系统运行质量的重要指标. 随着数字化和智能化的进程, 系统的可用性愈发重要。例如,制造业流水线需要高可用性的 MES 系统来保证流水线的正常运转。 本文为高可用相关的技术文档, 从以下几个方面对高可用在制造业的应用和实施进行详细描述。 1.1 可用性(Availability)定义