HashMap遍历
HashMap 遍历从大的方向来说,可分为以下 4 类:
-
迭代器(Iterator)方式遍历;
-
For Each 方式遍历;
-
Lambda 表达式遍历(JDK 1.8以后);
-
Streams API 遍历(JDK 1.8以后)。
但每种类型下又有不同的实现方式,因此具体的遍历方式又可以分为以下 7 种:
-
使用迭代器EntrySet 的方式进行遍历;
-
使用迭代器KeySet 的方式进行遍历;
-
使用 For Each EntrySet 的方式进行遍历;
-
使用 For Each KeySet 的方式进行遍历;
-
使用 Lambda 表达式的方式进行遍历;
-
使用 Streams API 单线程的方式进行遍历;
-
使用 Streams API 多线程的方式进行遍历;
性能分析
1. Streams API 多线程 > 迭代器EntrySet ≈ For Each EntrySet > Streams API 单线程 > 迭代器KeySet ≈ For Each KeySet
2. entrySet
的性能比 keySet
的性能高出了一倍左右,因此我们应该尽量使用 entrySet
来实现 Map 集合的遍历。
3. 通过迭代器循环和 for
循环的遍历的 EntrySet 或者KeySet最终生成的字节码是一样的,因此其性能大致相同
为什么EntrySet
比 KeySet
的性能高一倍?
KeySet
在使用迭代器或者 for 循环时,已经遍历了一遍 Map 集合了,再用 map.get(key)
查询时,相当于遍历了两遍集合。而 EntrySet
只遍历了一遍 Map 集合,之后把 key
和 value
值都放入到了 Entry
对象中,因此再获取 key
和 value
值时就无需再遍历 Map 集合。所以EntrySet
的性能比 KeySet
的性能高出了一倍,因为 KeySet
相当于循环了两遍 Map 集合,而 EntrySet
只循环了一遍。
迭代遍历过程为什么不能直接使用map.remove()删除元素?
我们不能在遍历中使用集合 map.remove()
来删除数据,这是非安全的操作方式,但我们可以使用迭代器的 iterator.remove()
的方法来删除数据,是安全的删除集合的方式。
在使用HashMap进行遍历和删除操作时,不能在遍历过程中直接删除元素,这是因为HashMap的迭代器设计不支持在遍历时对集合进行结构性修改。当在遍历过程中直接删除元素时,会导致迭代器的状态与实际集合的状态不一致,可能引发ConcurrentModificationException(并发修改异常)。
具体来说,当创建HashMap的迭代器时,会生成一个"modCount"字段,表示HashMap结构性修改的次数。每当对HashMap进行插入、删除等操作时,"modCount"都会增加。而在迭代器遍历HashMap时,会将当前的"modCount"与之前保存的"expectedModCount"进行比较,如果两者不相等,则会抛出ConcurrentModificationException。