[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
本文来自博客园,作者:knqiufan,转载请注明原文链接:https://www.cnblogs.com/knqiufan/p/16670669.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本