[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接口的 ...
随机推荐
- P5730
这道题莫名其妙的在本地能过可是洛谷上却0分,把WA的点下载下来之后发现我输出的和他要输出的明明一模一样,说明洛谷的评测有一些问题.我把getchar输入换成cin输入后就AC了,说明洛谷对getcha ...
- Bash 常用命令总结
基础常用命令 某个命令 --h,对这个命令进行解释 某个命令 --help,解释这个命令(更详细) man某个命令,文档式解释这个命令(更更详细)(执行该命令后,还可以按/+关键字进行查询结果的搜索) ...
- 每天学五分钟 Liunx 110 | 存储篇:RAID
RAID RAID 是廉价磁盘冗余阵列(Redundant Array of Inexpensive Disks)的意思.通过它可以将较小的磁盘组成较大的磁盘. RAID 模式 RAID 有几种模 ...
- 初次安装Linux 1Panel面板体验
初次安装Linux 1Panel面板体验 1Panel是Linux下的一款服务器管理工具.和宝塔相比更加轻量化.相比之下各有优点,本文让我们一起来安装1Panel面板. 面板优势 快速建站 :深度集成 ...
- TLS1.3的简单学习
TLS1.3的简单学习 TLS的历史 From GTP3.5 TLS(传输层安全)是一种加密协议,旨在确保 Internet 通信的安全性和隐私保护.下面是 TLS 的历史概述: SSL(安全套接层) ...
- tempfs 的再学习
tempfs 的再学习 背景 最近学习研究linux的内存buffer 和 cache相关的知识. 发现对linux的VFS的理解其实非常不到位. 再验证内存的使用的page caches和 drop ...
- [转帖]CentOS-7-x86_64-Everything-2009 rpm包列表(CentOS7.9)
CentOS-7-x86_64-Everything-2009 rpm包列表(CentOS7.9) 共10073个文件 复制389-ds-base-1.3.10.2-6.el7.x86_64.rpm ...
- [转帖]Linux:CPU频率调节模式以及降频方法简介
概述 cpufreq的核心功能,是通过调整CPU的电压和频率,来兼顾系统的性能和功耗.在不需要高性能时,降低电压和频率,以降低功耗:在需要高性能时,提高电压和频率,以提高性能. cpufreq 是一个 ...
- 【转帖】Linux创建软连接出现的错误及解决方法
问题: 创建软连接以后,使用cd 软连接路径显示没有那个文件或目录. 创建过程及切换结果,如图: 原因: 路径一定要写成绝对路径!!! 解决办法: 了解创建软连接的命令: ln -s 源文件 目标文件 ...
- [转载]关于NSA的EternalBlue(永恒之蓝) ms17-010漏洞利用
2017年5月19日 感谢原作者:http://www.cnblogs.com/cnbluerain/ 好久没有用这个日志了,最近WannaCry横行,媒体铺天盖地的报道,我这 ...