HashMap介绍
HashMap遍历方式
HashMap的遍历,大体上可分为4类,而每种类型下又有不同的实现方式,总共的遍历方式可分为7种:
- 迭代器遍历:
- 使用迭代器对EntrySet遍历;
- 使用迭代器对KeySet遍历;
- foreach遍历:
- 使用foreach对EntrySet遍历;
- 使用foreach对KeySet遍历;
- lambda表达式遍历;
- streams API遍历:
- Streams API单线程方式遍历;
- Streams API多线程方式遍历。
下面列举出几种遍历方式,首先创建并初始化一个HashMap:
public class HashMapTest {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "Java");
map.put(2, "Python");
map.put(3, "C++");
map.put(4, "Rust");
map.put(5, "Go");
System.out.println("1. 迭代器遍历EntrySet");
traverseByIterator1(map);
System.out.println("2. 迭代器遍历KeySet");
traverseByIterator2(map);
System.out.println("3. foreach遍历EntrySet");
traverseByForeach1(map);
System.out.println("4. foreach遍历KeySet");
traverseByForeach2(map);
System.out.println("5. lambda表达式遍历");
traverseByLambda(map);
System.out.println("6. streams API单线程");
traverseByStreams1(map);
System.out.println("7. streams API多线程");
traverseByStreams2(map);
}
}
迭代器
EntrySet
public static void traverseByIterator1(Map map) {
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
输出:
1. 迭代器遍历EntrySet
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
KeySet
public static void traverseByIterator2(Map map) {
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key + " : " + map.get(key));
}
}
输出:
2. 迭代器遍历KeySet
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
foreach
对于foreach遍历,内部也是通过创建迭代器来遍历
EntrySet
public static void traverseByForeach1(Map<Integer, String> map) {
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
输出:
3. foreach遍历EntrySet
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
KeySet
public static void traverseByForeach2(Map<Integer, String> map) {
for (Integer key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
}
输出:
4. foreach遍历KeySet
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
lambda表达式
public static void traverseByLambda(Map map) {
map.forEach((key, value) -> {
System.out.println(key + " : " + value);
});
}
输出:
5. lambda表达式遍历
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
streams API
单线程
public static void traverseByStreams1(Map<Integer, String> map) {
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey() + " : " + entry.getValue());
});
}
输出:
6. streams API单线程
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
多线程
public static void traverseByStreams2(Map<Integer, String> map) {
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + " : " + entry.getValue());
});
}
输出:
7. streams API多线程
1 : Java
2 : Python
3 : C++
4 : Rust
5 : Go
遍历时删除元素
在上述几种遍历方式中,有些可以在遍历过程中安全删除元素,有些则会抛出ConcurrentModificationException异常,这是因为遍历过程中会比较modCount != expectedModCount
,不相等就会抛出异常,具体分析请往下看。
迭代器:
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
if (entry.getKey() == 1) {
iterator.remove();
}
}
通过迭代器遍历,并使用迭代器的remove()
方法可以正常删除元素。
成功的原因是,调用iterator.remove()
方法最后会对expectedModCount
值进行更新,这样就保证了迭代器调用next()
获取下一个元素时,检查modCount == expectedModCount
。
foreach:
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() == 2) {
map.remove(entry.getKey());
}
}
在foreach遍历的过程中调用Map的remove()
方法会抛出ConcurrentModificationException异常。
通过查看源码得知,抛出异常的原因是删除元素之后,进行下一个元素的遍历时,比较变量modCount
与expectedModCount
不相等:
HashMap中的变量modCount
记录了HashMap的修改次数,HashIterator中的变量expectedModCount
在遍历前会初始化与modCount
相等,当删除一个元素时,++modCount
,之后迭代器通过next()
获取下一个元素时,检查modCount != expectedModCount
,就会抛出异常。
此外,这种检查到错误就抛出异常并停止程序后续执行的机制被称为fail-fast机制。
lambda:
map.forEach((key, value) -> {
if (key == 1) {
map.remove(key);
}
});
使用lambda表达式遍历时删除也会抛出ConcurrentModificationException。
可以通过removeIf()
对key进行判断后删除。
map.keySet().removeIf(key -> key == 1);
map.forEach((key, value) -> {
System.out.println(key + value);
});
sterams:
map.entrySet().stream().forEach((entry) -> {
if (entry.getKey() == 1) {
map.remove(entry.getKey());
}
});
使用stream遍历删除同样抛出ConcurrentModificationException。
可以使用filter()
过滤掉不需要的数据再遍历,但是这种方式不会真正删除hashmap中的元素。
map.entrySet().stream().filter(e -> 1 != e.getKey()).forEach((entry) -> {
if (entry.getKey() == 1) {
System.out.println(entry.getKey());
}
});