[Java]Map接口有关总结
Map接口
1、HashMap和Hashtable的区别
线程安全方面。
HashMap
是非线程安全的,Hashtable
是线程安全的。因为Hashtable
内部方法基本都经过synchronized
修饰。但是如果要保证线程安全推荐使用ConcurrentHashMap
。效率方面。因为线程安全问题
HashMap
要比Hashtable
效率高一点。但Hashtable
基本被淘汰,尽量不要在代码中使用。对null的支持方面。
HashMap
的key
和value
都支持null
,作为key只能有一个为null
,作为value可以有多个``null
。Hashtable
的key
和value
都不允许有null
值。初始容量。
- 创建时不指定容量:
Hashtable
的默认初始容量是11,之后每次扩充,容量变为原来的2n+1
;HashMap
的默认初始容量是16,每次扩容变为原来的2倍。 - 创建时指定容量:
Hashtable
会直接使用给定的大小,而HashMap
会扩容为2的幂次方大小。
所以
HashMap
总是使用2的幂作为哈希表的大小。- 创建时不指定容量:
底层数据结构方面。
JDK1.8
以后的HashMap
在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间。Hashtable
没有这样的机制。
2、HashMap和HashSet的区别
HashSet
的底层是基于HashMap
实现的。HashSet
的源码很少,除了 clone()
、writeObject()
、readObject()
是 HashSet
自己不得不实现之外,其他方法都是直接调用 HashMap
中的方法。
HashMap |
HashSet |
---|---|
实现了Map 接口 |
实现Set 接口 |
存储键值对 | 仅存储对象 |
调用put 向map 中添加元素 |
调用add 向Set 中添加元素 |
使用Key 计算hashcode |
HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals() 方法用来判断对象的相等性 |
3、HashMap和TreeMap的区别
TreeMap
和HashMap
都继承自AbstractMap
,但是TreeMap
还实现了NavigableMap
接口和SortedMap
接口。
实现 NavigableMap
接口让 TreeMap
有了对集合内元素的搜索的能力。
实现SortedMap
接口让 TreeMap
有了对集合中的元素根据键排序的能力。
默认是按 key 的升序排序,不过我们也可以指定排序的比较器。例如:
// 通过传入匿名内部类的方式实现
TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person person1, Person person2) {
int num = person1.getAge() - person2.getAge();
return Integer.compare(num, 0);
}
});
// 也可以通过 Lambda 表达式实现
TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
int num = person1.getAge() - person2.getAge();
return Integer.compare(num, 0);
});
相比于HashMap
来说 TreeMap
主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力
4、HashTable和ConcurrentHashMap的区别
ConcurrentHashMap
和 Hashtable
的区别主要体现在实现线程安全的方式上不同。
底层数据结构,在JDK1.8
之前ConcurrentHashMap
底层采用 分段的数组+链表 实现,JDK1.8
采用的数据结构跟 HashMap1.8
的结构一样,数组+链表/红黑二叉树。
Hashtable
和 JDK1.8
之前的 HashMap
的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap
的主体,链表则是主要为了解决哈希冲突而存在的。
主要就是实现线程安全的方式的不同:
- 在
JDK1.7
的时候,ConcurrentHashMap
对整个桶数组进行了分割分段(Segment
,分段锁),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 - 到了
JDK1.8
的时候,ConcurrentHashMap
已经摒弃了Segment
的概念,而是直接用Node
数组+链表+红黑树的数据结构来实现,并发控制使用synchronized
和CAS
来操作。(JDK1.6
以后synchronized
锁做了很多优化) 整个看起来就像是优化过且线程安全的HashMap
,虽然在JDK1.8
中还能看到Segment
的数据结构,但是已经简化了属性,只是为了兼容旧版本; Hashtable
(同一把锁) :使用synchronized
来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put
添加元素,另一个线程不能使用put
添加元素,也不能使用get
,竞争会越来越激烈效率越低。
来看看一些对比图:
对于JDK1.7
,ConcurrentHashMap
是由 Segment
数组结构和 HashEntry
数组结构组成。
Segment
数组中的每个元素包含一个 HashEntry
数组,每个 HashEntry
数组属于链表结构。
对于JDK1.8
, ConcurrentHashMap
不再是 Segment
数组 + HashEntry
数组 + 链表,而是 Node
数组 + 链表 / 红黑树。不过,Node
只能用于链表的情况,红黑树的情况需要使用 TreeNode
。当冲突链表达到一定长度时,链表会转换成红黑树。
5、HashSet怎么检查重复?
在 JDK1.8
中,HashSet
的add()
方法只是简单的调用了HashMap
的put()
方法,并且判断了一下返回值以确保是否有重复元素。
浅看一下HashSet
的源码:
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
而HashMap
的put方法内部调用了putVal
方法,通过查看源码可以看到关于putVal
的说明:
// Returns : previous value, or null if none
// 返回值:如果插入位置没有元素返回null,否则返回上一个元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
}
6、HashMap的长度为什么是2的幂次方
原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀。
Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。
所以用之前要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash
”。(n 代表数组长度)。这也就解释了 HashMap
的长度为什么是 2 的幂次方。
为什么取模运算是 (n - 1) & hash
呢?
取模(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)
又因为采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。
7、HashMap的7种遍历方式
使用迭代器(Iterator)EntrySet 的方式进行遍历
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
使用迭代器(Iterator)KeySet 的方式进行遍历;
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
使用 For Each EntrySet 的方式进行遍历;
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
使用 For Each KeySet 的方式进行遍历;
for (Integer key : map.keySet()) {
System.out.println(key);
System.out.println(map.get(key));
}
使用 Lambda 表达式的方式进行遍历;
map.forEach((key, value) -> {
System.out.println(key);
System.out.println(value);
});
使用 Streams API 单线程的方式进行遍历;
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
使用 Streams API 多线程的方式进行遍历。
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
通过 Oracle 官方提供的性能测试工具 JMH 对7种遍历方式进行性能测试可以得出结论:
其中 Units 为 ns/op 意思是执行完成时间(单位为纳秒),而 Score 列为平均执行时间, ±
符号表示误差。
因为 parallelStream 为多线程版本性能一定是最好的,所以就不参与测试了。
从以上结果可以看出,两个 entrySet
的性能相近,并且执行速度最快,接下来是 stream
,然后是两个 keySet
,性能最差的是 KeySet
。
从以上结果可以看出 entrySet
的性能比 keySet
的性能高出了一倍之多,因此我们应该尽量使用 entrySet
来实现 Map 集合的遍历。
8、HashMap多线程操作死循环问题
并发先的rehash操作会产生死循环问题。
详细文章解析可参考:https://coolshell.cn/articles/9606.html
[Java]Map接口有关总结的更多相关文章
- Java—Map接口中的常用方法
Map接口与Collection接口的区别 Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储. Map中的集合,元素是成对存在的(理解为夫妻).每 ...
- Java Map接口
Map接口映射唯一键的值.一个关键是,要使用在日后检索值对象. 给定一个键和一个值,可以在一个Map对象存储的值.后的值被存储时,可以使用它的键检索. 抛出一个NoSuchElementExcepti ...
- Java Map 接口
Map接口中键和值一一映射. 可以通过键来获取值. 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值. 当访问的值不存在的时候,方法就会抛出一个NoSuchEl ...
- java中集合类中Collection接口中的Map接口的常用方法熟悉
1:Map接口提供了将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值.Map接口中同样提供了集合的常用方法. 2:由于Map集合中的元素是通过key,value,进行存储的,要 ...
- Java集合中Map接口的使用方法
Map接口 Map提供了一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value: Map中的键值对以Entry类型的对象实例形式存在: 建(key值 ...
- JAVA ,Map接口 ,迭代器Iterator
1. Map 接口概述 java.util.Map 接口描述了映射结构, Map 接口允许以键集.值集合或键 - 值映射关系集的形式查看某个映射的内容. Java 自带了各种 Map 类. 这些 ...
- Java集合框架中Map接口的使用
在我们常用的Java集合框架接口中,除了前面说过的Collection接口以及他的根接口List接口和Set接口的使用,Map接口也是一个经常使用的接口,和Collection接口不同,Map接口并不 ...
- Java集合之Map接口
Map使用键值对来存储数据,将键映射到值对象,一个映射不能包含重复的键,每一个键最多只能映射到一个值.Map接口的具体实现类:HashMap,Hashtable,TreeMap,LinkedHashM ...
- Java集合——Map接口
1.定义 Map用于保存存在映射关系<key,value>的数据.其中,key值不能重复(使用equals()方法比较),value值可以重复 2.方法 V put(key,value) ...
- Java API —— Map接口
1.Map接口概述 · 将键映射到值的对象 · 一个映射不能包含重复的键 · 每个键最多只能映射到一个值 2.Map接口和Collection接口的 ...
随机推荐
- shell脚本(15)-sed命令
文档目录一.sed-简介1.shell对文件操作介绍2.sed命令简介3.sed语法: sed [options] '{command}{flags}' [filename]二.sed-命令选项( - ...
- 解决Xshell/Xftp提示“要继续使用此程序必须应用到最新的更新或者新版本”(临时规避和彻底解决方案)
一.xshell与xftp登录时提示,但是更新却又每次都失败,无法登录 二. 临时规避方案:手动修改日期为1年前,问题解决软件可以打开,但是每次启动都要手动修改,甚是麻烦 三.彻底解决方案,修改xs ...
- [AGC058C] Planar Tree 题解
前言 赛时没做出来,赛后把题补了.果然是 maroonrk 出的,名不虚传啊--真的很好的一道题目. 解法 题目中的圆周有以下几个性质: 圆周上如果有相邻的等值,我们可以去掉一个而不改变答案(这个很好 ...
- 使用命令行方式搭建uni-app + Vue3 + Typescript + Pinia + Vite + Tailwind CSS + uv-ui开发脚手架
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 在TypeScript项目中搭配Axios封装后端接口调用
前言 本来是想发 next.js 开发笔记的,结果发现里面涉及了太多东西,还是拆分出来发吧~ 本文记录一下在 TypeScript 项目里封装 axios 的过程,之前在开发 StarBlog-Adm ...
- MySQL高可用搭建方案之(MHA)
有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 原文地址 MHA架构介绍 MHA是Master High Av ...
- [转帖]prometheus node-exporter 全部指标说明
https://www.cnblogs.com/276815076/p/16383615.html Basic CPU / Mem / Disk Info Basic CPU / Mem / Disk ...
- [转帖]Active Session History (ASH)
Introduction V$ACTIVE_SESSION_HISTORY DBA_HIST_ACTIVE_SESS_HISTORY Enterprise Manager Performance Pa ...
- [转帖]java -D参数设置系统属性无效问题及解决
https://www.jb51.net/article/271236.htm 这篇文章主要介绍了java -D参数设置系统属性无效问题及解决方案,具有很好的参考价值,希望对大家有所帮助.如有错误 ...
- [转帖]一文浅析Nginx线程池!
https://zhuanlan.zhihu.com/p/616500765 Nginx通过使用多路复用IO(如Linux的epoll.FreeBSD的kqueue等)技术很好的解决了c10k ...