[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接口的 ...
随机推荐
- Vue项目中使用 tinymce 富文本编辑器的方法,附完整源码
Vue项目中使用 tinymce 富文本编辑器的方法,附完整源码 https://blog.csdn.net/snsHL9db69ccu1aIKl9r/article/details/11432414 ...
- 开发人员常用Docker指令
什么是 Docker? Docker 是一个开源的容器化平台,用于构建.打包和运行应用程序.它允许开发者将应用程序及其依赖项打包成一个独立的可移植容器,可以在任何环境中运行,无论是开发环境.测试环境还 ...
- JVM 性能调优 及 为什么要减少 Full GC
本文为博主原创,未经允许不得转载: 系统上线压测,需要了解系统的瓶颈以及吞吐量,并根据压测数据进行对应的优化. 对压测进行 JVM 性能优化,有两条思路: 第一种情况 : 使用压测工具 jmeter ...
- 神经网络优化篇:详解学习率衰减(Learning rate decay)
学习率衰减 加快学习算法的一个办法就是随时间慢慢减少学习率,将之称为学习率衰减,来看看如何做到,首先通过一个例子看看,为什么要计算学习率衰减. 假设要使用mini-batch梯度下降法,mini-ba ...
- 在线P图工具(基于minipaint开发)
在浏览github过程中,发现一个超级实用的仓库,viliulsle开发的minipaint,类似于photoshop的网页版.基于webpack开发的,打包非常简单,故自己搭建了一套. 在线预览 在 ...
- 07-verilog & sytem verilog
一.数据类型 二值逻辑变量 bit 不赋值的时候,变量初始默认为0 x或z的值会转变为0 bit vector--bit矢量 bit [msb,lsb] variable_name = [initia ...
- MoeCTF 2023(西电CTF新生赛)WP
个人排名 签到 hello CTFer 1.题目描述: [非西电] 同学注意: 欢迎你来到MoeCTF 2023,祝你玩的开心! 请收下我们送给你的第一份礼物: https://cyberchef.o ...
- c# 创建一个只接收消息的窗口
/// <summary> /// WM_COPYDATA消息,进程间传输信息专用结构 /// </summary> public struct COPYDATASTRUCT ...
- 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.12.18)
一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...
- [转帖]tidb4.0.4使用tiup扩容TiKV 节点
https://blog.csdn.net/mchdba/article/details/108896766 环境:centos7.tidb4.0.4.tiup-v1.0.8 添加两个tikv节点 ...