首页 > 编程知识 正文

maputi(world map)

时间:2023-05-06 17:05:11 阅读:94051 作者:3771

作者|楼下hldnm

头图| CSDN从图虫下载

作为List聚集的兄弟Map,我们每天都在用。 一不小心就会踩到坑里。

今天把这些常见的坑总结起来,牵起自己的手,防止后续同学继续踩坑。

本文的设计知识点如下

并不是所有的Map都包括在内

这个踩坑的经历还是在实习的时候发生的。 那时有这样的业务代码。 功能很简单,从XML中读取相关配置并存储在Map中。

代码示例如下:

那时正好有个小需求,需要更改这个业务代码。 的更改过程中,突然想到了HashMap并发进程可能会导致死锁的问题。

因此,我们更改了此代码,并将HashMap更改为ConcurrentHashMap。

滋滋提交了代码。 然后当天上线的时候,我意识到破裂了。

在APP启动过程中发生了NPE问题,导致APP启动失败。

根据异常日志,很快就查明了问题的原因。 由于XML配置问题,读取元素为,元素位于ConcurrentHashMap中,并抛出了空指针异常。

这不科学啊。 一直以来HashMap都可以正常存在,为什么弟弟的ConcurrentHashMap不行呢?

如果翻阅ConcurrentHashMap#put方法的源代码,会在开头看到KV的空判定检查。

看到这里,你有没有怀疑过为什么ConcurrentHashMap和HashMap的设计判断逻辑不同呢?

向万能的谷歌求助,找到了Doug Lea爷爷的答案:

总结:

引起歧义。 如果value为,则不知道是值还是key没有映射具体值。

我不喜欢Doug Lea,以为是隐藏的炸弹。

上面说的Josh Bloch正是HashMap的作者,他和Doug Lea在问题上意见不一致。

因此,ConcurrentHashMap和HashMap的处理可能不同。

最后,粘贴常用的Map子类集合以查看存储情况。

上面的实现系制约,都太不同了,我有点记不住。 其实,在我们加入要素之前,如果自主清空指针进行判断,不存入Map,就可以冷静地避免上述问题。

自定义对象为key

让我们先来看一个简单的例子。 定制Goods商品类,并将其作为密钥存在于Map中。

示例代码如下:

在上面的代码中,第二次添加了同样的商品。 本来,我们期待新添加的值会被原来的旧值代替。 但是,实际上这里没有成功替换,而是添加了一对键值。

让我们看看HashMap#put的源代码。

以下代码基于JDK1.7

在此,首先判断通过混列计算的混列,如果相等则判断equals的结果。 但是,由于没有重写Goods对象的hashCode和equals方法,缺省情况下

hashCode 将会使用父类对象 Object 方法逻辑。

而 Object#hashCode 是一个 native 方法,默认将会为每一个对象生成不同 hashcode(与内存地址有关),这就导致上面的情况。

所以如果需要使用自定义对象做为 Map 集合的 key,那么一定记得重写hashCode 与 equals 方法。

然后超帅的网络为自定义对象重写上面两个方法,接下去又可能踩坑另外一个坑。

使用 lombok 的 EqualsAndHashCode 自动重写 hashCode 与 equals 方法。

上面的代码中,当 Map 中置入自定义对象后,接着修改了商品金额。然后当我们想根据同一个对象取出 Map 中存的值时,却发现取不出来了。

上面的问题主要是因为 get 方法是根据对象 的 hashcode 计算产生的 hash 值取定位内部存储位置。

当我们修改了金额字段后,导致 Goods 对象 hashcode 产生的了变化,从而导致 get 方法无法获取到值。

通过上面两种情况,可以看到使用自定义对象作为 Map 集合 key,还是挺容易踩坑的。

所以尽量避免使用自定义对象作为 Map 集合 key,如果一定要使用,记得重写 hashCode 与 equals 方法。另外还要保证这是一个不可变对象,即对象创建之后,无法再修改里面字段值。

错用 ConcurrentHashMap 导致线程不安全

之前的文章『每天都在用 Map,这些核心技术你知道吗?』我们说过 HashMap 是一个线程不安全的容器,多线程环境为了线程安全,我们需要使用 ConcurrentHashMap代替。

但是不要认为使用了 ConcurrentHashMap 一定就能保证线程安全,在某些错误的使用场景下,依然会造成线程不安全。

上面示例代码,我们原本期望输出 1001,但是运行几次,得到结果都是小于 1001。

深入分析这个问题原因,实际上是因为第一步与第二步是一个组合逻辑,不是一个原子操作。

ConcurrentHashMap 只能保证这两步单的操作是个原子操作,线程安全。但是并不能保证两个组合逻辑线程安全,很有可能 A 线程刚通过 get 方法取到值,还未来得及加 1,线程发生了切换,B 线程也进来取到同样的值。

这个问题同样也发生在其他线程安全的容器,比如 Vector等。

上面的问题解决办法也很简单,加锁就可以解决,不过这样就会使性能大打折扣,所以不太推荐。

我们可以使用 AtomicInteger 解决以上的问题。

List 集合这些坑,Map 中也有

上一篇文章中我们提过,Arrays#asList 与 List#subList 返回 List 将会与原集合互相影响,且可能并不支持 add 等方法。同样的,这些坑爹的特性在 Map 中也存在,一不小心,将会再次掉坑。

Map 接口除了支持增删改查功能以外,还有三个特有的方法,能返回所有 key,返回所有的 value,返回所有 kv 键值对。

// 返回 key 的 set 视图Set<K> keySet;// 返回所有 value Collection 视图Collection<V> values;// 返回 key-value 的 set 视图Set<Map.Entry<K, V>> entrySet;

这三个方法创建返回新集合,底层其实都依赖的原有 Map 中数据,所以一旦 Map 中元素变动,就会同步影响返回的集合。

另外这三个方法返回新集合,是不支持的新增以及修改操作的,但是却支持 clear、remove 等操作。

示例代码如下:

所以如果需要对外返回 Map 这三个方法产生的集合,建议再来个套娃。

new ArrayList<>(map.values);

最后再简单提一下,使用 foreach 方式遍历新增/删除 Map 中元素,也将会和 List 集合一样,抛出 ConcurrentModificationException。

总结

从上面文章可以看到不管是 List 提供的方法返回集合,还是 Map 中方法返回集合,底层实际还是使用原有集合的元素,这就导致两者将会被互相影响。所以如果需要对外返回,请使用套娃大法,这样让别人用的也安心。

第二, Map 各个实现类对于 的约束都不太一样,这里建议在 Map 中加入元素之前,主动进行空指针判断,提前发现问题。

第三,慎用自定义对象作为 Map 中的 key,如果需要使用,一定要重写 hashCode 与 equals 方法,并且还要保证这是个不可变对象。

第三,ConcurrentHashMap 是线程安全的容器,但是不要思维定势,不要片面认为使用 ConcurrentHashMap 就会线程安全。

版权声明:本文为CSDN博主「楼下hldnm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/u014634309/java/article/details/105964392

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。