你不知道的Map家族中的那些冷门容器
概述
本篇文章主要讲解下Map家族中3个相对冷门的容器,分别是WeakHashMap、EnumMap、IdentityHashMap, 想必大家在平时的工作中也很少用到,或者压根不知道他们的特性以及适用场景,本篇文章就带你一探究竟。
WeakHashMap
介绍
WeakHashMap称为弱三列映射,实现了Map接口,具有如下特性:
- WeakHashMap中的entry是一个弱引用,当除了自身有对key的引用外,此key没有其他引用,那么GC之后此map会自动丢弃此值。
- 不是线程安全的
- 可以存储null
演示案例
public static void main(String[] args) {
String a = new String("a");
String b = new String("b");
Map weakmap = new WeakHashMap();
weakmap.put(a, "aaa");
weakmap.put(b, "bbb");
a = null;
b = null;
// 进行gc
System.gc();
Iterator j = weakmap.entrySet().iterator();
while (j.hasNext()) {
Map.Entry en = (Map.Entry) j.next();
System.out.println("weakmap:" + en.getKey() + ":" + en.getValue());
}
}
运行结果:
已经被gc回收了。
原理实现
从这里我们可以看到其内部的Entry继承了WeakReference,也就是弱引用,所以就具有了弱引用的特点。
弱引用的特点是在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
WeakReference中有个成员变量ReferenceQueue,他的作用是GC会清理掉对象之后,引用对象会被放到ReferenceQueue中,然后遍历这个queue进行删除即可Entry。WeakHashMap内部有一个expungeStaleEntries函数,在这个函数内部实现移除其内部不用的entry从而达到的自动释放内存的目的。因此我们每次访问WeakHashMap的时候,都会调用这个expungeStaleEntries函数清理一遍。
使用场景
在如今的并发泛滥的大环境下,大家应该都用过缓存,缓存都是放在内存中的,而内存几乎是计算机中最宝贵也是最稀缺的资源,所以需要谨慎的使用,不然很容易就出现 OOM。缓存的主要作用是为了更快的处理业务、降低服务器的压力,那么就要保证缓存命中率,这里假设整个缓存是一个 key-value 结构的(以键值对缓存为例),HashMap 作为强引用对象在没有主动将 key 删除时是不会被 JVM 回收的,这样 HashMap 中的对象就会越积越多直到 OOM 错误;那么如何做到既让缓存的命中率高又不占用那么多的内存,这里就可以采用 WeakHashMap,当然不会有 HashMap 100% 的命中率(假设内存足够),但是在保证程序正常的前提下更好的实现了缓存这套解决方案。
EnumMap
介绍
用于枚举类型键的专用Map实现。枚举映射中的所有键必须来自创建映射时显式或隐式指定的单个枚举类型。
相对于HashMap中枚举作为key, EnumMap内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。
- 不是线程安全的
- 可以存放null值
演示案例
public static void main(String[] args) {
// 构造函数传入类型
Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
map.put(DayOfWeek.WEDNESDAY, "星期三");
map.put(DayOfWeek.THURSDAY, "星期四");
map.put(DayOfWeek.FRIDAY, "星期五");
map.put(DayOfWeek.SATURDAY, "星期六");
map.put(DayOfWeek.SUNDAY, "星期日");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));
}
enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
原理实现
put方法源码如下:
public V put(K key, V value) {
// 对枚举类型进行检查,看key和构造函数传入的class类型是否一致
typeCheck(key);
// 枚举的顺序
int index = key.ordinal();
// 原来位置的值
Object oldValue = vals[index];
// 设置值
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
通过put源码发现是通过数组的方式实现存储,而且也不需要进行扩容。
使用场景
如果项目中遇到针对枚举作为key的映射容器,可以优先选择EnumMap。
IdentityHashMap
介绍
该类使用散列表实现Map接口,在比较键(和值)时使用引用相等代替对象相等。换句话说,在一个IdentityHashMap中,当且仅当(k1k2)两个键k1和k2被认为是相等的。(在正常的Map实现(如HashMap)两个键k1和k2被认为是相等的,当且仅当(k1null ?k2 = = null: k1.equals (k2)))。
- 不是线程安全的
- 无序
- key不可以是null
演示案例
public static void main(String[] args) {
// hashMap
Map<Integer, String> hashMap = new HashMap<>();
// identityHashMap
Map<Integer, String> identityHashMap = new IdentityHashMap<>();
hashMap.put(new Integer(200), "a");
hashMap.put(new Integer(200), "b");
identityHashMap.put(new Integer(200), "a");
identityHashMap.put(new Integer(200), "b");
//遍历hashmap
System.out.println("hashmap 结果:");
hashMap.forEach((key, value) -> {
System.out.println("key = " + key + ", value = " + value);
});
//遍历hashmap
System.out.println("identityHashMap 结果:");
identityHashMap.forEach((key, value) -> {
System.out.println("key = " + key + ", value = " + value);
});
}
运行结果:
原理实现
IdentityHashMap底层的数据结构就是数组,我们关注下put方法:
调用hash方法,获取key在table的位置index,然后进行赋值操作,也是分成了3种情况:
1.item == k,找到了对应的key,value存在key右相邻的位置,对tab[i + 1]进行更新,并返回原来的值;
2.item == null,表示table中没有对应的key值,跳出for循环,执行tab[i] = k和tab[i + 1] = value进行新key的插入操作。个人觉得这里的扩容时机选择的不太好,好不容易找到的更新位置,因为扩容给整没了,还得再次重新计算,可以和HashMap一样,在更新后再扩容。
3.item != null && item != key,表示hash冲突发生,调用nextKeyIndex获取处理冲突后的index位置,然后重复上面的过程。
我们再来看下hash方法:
IdentityHashMap中获取hash值采用的System.identityHashCode方法,在不重写Object.hashCode方法时,System.identityHashCode和Object.hashCode返回的值相同,相当于对象的唯一的HashCode。System.identityHashCode(null)始终返回0, 无论是否重写Object.hashCode,都不影响System.identityHashCode的执行结果。
使用场景
当我们必须使用地址相等来判断值相等的场合,以及我们确定只要其地址不相等,则其equals方法的结果也必定不相等的场合。
总结
本文主要讲解了集中不常用的Map, 当然我们也需要了解他们的特性,在有些时候还是会用到的。
如果本文对你有帮助的话,请留下一个赞吧
欢迎关注个人公众号——JAVA旭阳
更多学习资料请移步:程序员成神之路
你不知道的Map家族中的那些冷门容器的更多相关文章
- C++中常用到的容器
这里主要讲C++中经常用到的一些保存数据的容器,其中也会介绍string. 在C++11中提到了很多容器,这里主要介绍:vector.list.map.还有一些其他的容器就不做介绍了. 1.Strin ...
- 关于Array的map方法中回调函数参数的问题
开门见山,我们先来看两个例子. var arr=['1','4','9','16']; var r=arr.map(Math.sqrt); 猜猜r的结果会是多少? 没错就是 [1,2,3,4] 我们再 ...
- Map java中的map 如何修改Map中的对应元素
Map java中的map 如何修改Map中的对应元素 Map以按键/数值对的形式存储数据,和数组非常相似,在数组中存在的索引,它们本身也是对象. Map的接口 Map ...
- 集群: 如何在spring 任务中 获得集群中的一个web 容器的端口号?
系统是两台机器, 跑四个 web 容器, 每台机器两个容器 . nginx+memcached+quartz集群,web容器为 tomcat . web 应用中 用到spring 跑多个任务,任务只能 ...
- WPF中实现自定义虚拟容器(实现VirtualizingPanel)
WPF中实现自定义虚拟容器(实现VirtualizingPanel) 在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容 ...
- Map集合中value()方法与keySet()、entrySet()区别
http://blog.csdn.net/liu826710/article/details/9001254 在Map集合中 values():方法是获取集合中的所有的值----没有键,没有对应关系, ...
- 键盘录入一个文件夹路径,统计该文件夹(包含子文件夹)中每种类型的文件及个数,注意:用文件类型(后缀名,不包含.(点),如:"java","txt")作为key, 用个数作为value,放入到map集合中,遍历map集合
package cn.it.zuoye5; import java.io.File;import java.util.HashMap;import java.util.Iterator;import ...
- 获取map集合中key、value
获取Map集合类中key.value的两种方法 方法一:利用Set集合中的keySet()方法 Map<String,String> map = new HashMap<String ...
- 如何批量删除Docker中已经停止的容器
如何批量删除Docker中已经停止的容器 方法一: #显示所有的容器,过滤出Exited状态的容器,取出这些容器的ID, sudo docker ps -a|grep Exited|awk '{p ...
- 根据key删除Map集合中的key-value映射
一:在遍历Map时是不可以删除key-value映射的,如果根据key删除,如下: public static void main(String[] args) { Map<String,Obj ...
随机推荐
- 分布式存储系统之Ceph集群存储池、PG 与 CRUSH
前文我们了解了ceph集群状态获取常用命令以及通过ceph daemon.ceph tell动态配置ceph组件.ceph.conf配置文件相关格式的说明等,回顾请参考https://www.cnbl ...
- 为Azure-云准备一个基于Red Hat 8.x 的虚拟机镜像
由于公司最近要求部分项目上线到Azure云上,要求操作系统使用的Redhat 8.x,而且必须加固 而在Azure官网提供的镜像中,又没有Redhat,于是只有自己自定义Redhat镜像,最后加固,作 ...
- HDU3506 Monkey Party (区间DP)
一道好题...... 首先要将环形转化为线形结构,接着就是标准的区间DP,但这样的话复杂度为O(n3),n<=1000,要超时,所以要考虑优化. dp[i][j]=min( dp[i][k]+d ...
- Vue3 Vite3 状态管理 pinia 基本使用、持久化、在路由守卫中的使用
在<基于 vite 创建 vue3 项目>一文中整合了 pinia,有不少伙伴不知道 pinia 是什么,本文简单介绍 pinia.主要包括三方面: pinia 的基本用法,在<基于 ...
- 6.RabbitMQ系列之direct直连交换器
我们通过fanout exchange(扇型交换机)实现生产者发送一个消息,这个消息同时被传送给所有队列.但是有时我们不希望所有的消息都被所有队列接收,我们希望可以指定类型为a的消息只能被队列A接收, ...
- GitHub Pages 和 Jekyll 笔记
GitHub Pages 和 Jekyll 笔记 快速创建(使用默认的Jekyll引擎) 1. 新建仓库 新建一个空仓库, 名称为username.github.io, 其中 username 就是你 ...
- 题解 P6355 [COCI2007-2008#3] DEJAVU
kcm的原题.. 貌似是个组合数(? \(\sf {Solution}\) 对于每一个点,我们需要统计与它同一行的点数\(a\) 和同一列的点数\(b\) ,则该点对结果\(ans\) 的贡献为\(( ...
- 深度剖析Java的volatile实现原理,再也不怕面试官问了
上篇文章我们讲了synchronized的用法和实现原理,我们总爱说synchronized是重量级锁,volatile是轻量级锁.为什么volatile是轻量级锁,体现在哪些方面?以及volatil ...
- Vue报错:component has been registered but not used
原因: eslint代码检查到你注册了组件但没有使用,然后就报错了.比如代码: 比如Vue中注册了File组件,而实际上却没有使用到(直接取消注册为好): ... impor ...
- 基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换
有时候,为了给前端页面输出内容,有时候我们需要准备和数据库不一样的实体信息,因为数据库可能记录的是一些引用的ID或者特殊字符,那么我们为了避免前端单独的进行转义处理,我们可以在后端进行统一的格式化后再 ...