日常工作中 Map
絕對是我們 Java
程式設計師高頻使用的一種資料結構,那 Map
都有哪些遍歷方式呢?這篇文章阿粉就帶大家看一下,看看你經常使用的是哪一種。
透過 entrySet
來遍歷
1、透過 for
和 map.entrySet()
來遍歷
第一種方式是採用 for
和 Map.Entry
的形式來遍歷,透過遍歷 map.entrySet()
獲取每個 entry
的 key
和 value
,程式碼如下。這種方式一般也是阿粉使用的比較多的一種方式,沒有什麼花裡胡哨的用法,就是很樸素的獲取 ma
p 的 key
和 value
。
public static void testMap1(Map<Integer, Integer> map) {
long sum = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
sum += entry.getKey() + entry.getValue();
}
System.out.println(sum);
}
看過 HashMap
原始碼的同學應該會發現,這個遍歷方式在原始碼中也有使用,如下圖所示,
putMapEntries
方法在我們呼叫 putAll
方法的時候會用到。
2、透過 for
, Iterator
和 map.entrySet()
來遍歷
我們第一個方法是直接透過 for
和 entrySet()
來遍歷的,這次我們使用 entrySet()
的迭代器來遍歷,程式碼如下。
public static void testMap2(Map<Integer, Integer> map) {
long sum = 0;
for (Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator(); entries.hasNext(); ) {
Map.Entry<Integer, Integer> entry = entries.next();
sum += entry.getKey() + entry.getValue();
}
System.out.println(sum);
}
3、透過 while
,Iterator
和 map.entrySet()
來遍歷
上面的迭代器是使用 for
來遍歷,那我們自然可以想到還可以用 while
來進行遍歷,所以程式碼如下所示。
public static void testMap3(Map<Integer, Integer> map) {
Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
long sum = 0;
while (it.hasNext()) {
Map.Entry<Integer, Integer> entry = it.next();
sum += entry.getKey() + entry.getValue();
}
System.out.println(sum);
}
這種方法跟上面的方法類似,只不過迴圈從 for
換成了 while
,日常我們在開發的時候,很多場景都可以將 for
和 while
進行替換。2 和 3 都使用迭代器 Iterator
,透過迭代器的 next()
,方法來獲取下一個物件,依次判斷是否有 next
。
透過 keySet 來遍歷
上面的這三種方式雖然程式碼的寫法不同,但是都是透過遍歷 map.entrySet()
來獲取結果的,殊途同歸。接下來我們看另外的一組。
4、透過 for 和 map.keySet() 來遍歷
前面的遍歷是透過 map.entrySet()
來遍歷,這裡我們透過 map.keySet()
來遍歷,顧名思義前者是儲存 entry
的集合,後者是儲存 key
的集合,遍歷的程式碼如下,因為是 key
的集合,所以如果想要獲取 key
對應的 value
的話,還需要透過 map.get(key)
來獲取。
public static void testMap4(Map<Integer, Integer> map) {
long sum = 0;
for (Integer key : map.keySet()) {
sum += key + map.get(key);
}
System.out.println(sum);
}
5、透過 for
,Iterator
和 map.keySet()
來遍歷
public static void testMap5(Map<Integer, Integer> map) {
long sum = 0;
for (Iterator<Integer> key = map.keySet().iterator(); key.hasNext(); ) {
Integer k = key.next();
sum += k + map.get(k);
}
System.out.println(sum);
}
6、透過 while
,Iterator
和 map.keySet()
來遍歷
public static void testMap6(Map<Integer, Integer> map) {
Iterator<Integer> it = map.keySet().iterator();
long sum = 0;
while (it.hasNext()) {
Integer key = it.next();
sum += key + map.get(key);
}
System.out.println(sum);
}
我們可以看到這種方式相對於 map.entrySet()
方式,多了一步 get
的操作,這種場景比較適合我們只需要 key
的場景,如果也需要使用 value
的場景不建議使用 map.keySet()
來進行遍歷,因為會多一步 map.get()
的操作。
Java 8 的遍歷方式
注意下面的幾個遍歷方法都是是 JDK 1.8
引入的,如果使用的 JDK
版本不是 1.8
以及之後的版本的話,是不支援的。
7、透過 map.forEach()
來遍歷
JDK
中的 forEach
方法,使用率也挺高的。
public static void testMap7(Map<Integer, Integer> map) {
final long[] sum = {0};
map.forEach((key, value) -> {
sum[0] += key + value;
});
System.out.println(sum[0]);
}
該方法被定義在 java.util.Map#forEach
中,並且是透過 default
關鍵字來標識的,如下圖所示。這裡提個問題,為什麼要使用 default
來標識呢?歡迎把你的答案寫在評論區。
8、Stream
遍歷
public static void testMap8(Map<Integer, Integer> map) {
long sum = map.entrySet().stream().mapToLong(e -> e.getKey() + e.getValue()).sum();
System.out.println(sum);
}
9、ParallelStream
遍歷
public static void testMap9(Map<Integer, Integer> map) {
long sum = map.entrySet().parallelStream().mapToLong(e -> e.getKey() + e.getValue()).sum();
System.out.println(sum);
}
這兩種遍歷方式都是 JDK 8
的 Stream
遍歷方式,stream
是普通的遍歷,parallelStream
是並行流遍歷,在某些場景會提升效能,但是也不一定。
測試程式碼
上面的遍歷方式有了,那麼我們在日常開發中到底該使用哪一種呢?每一種的效能是怎麼樣的呢?為此阿粉這邊透過下面的程式碼,我們來測試一下每種方式的執行時間。
public static void main(String[] args) {
int outSize = 1;
int mapSize = 200;
Map<Integer, Integer> map = new HashMap<>(mapSize);
for (int i = 0; i < mapSize; i++) {
map.put(i, i);
}
System.out.println("---------------start------------------");
long totalTime = 0;
for (int size = outSize; size > 0; size--) {
long startTime = System.currentTimeMillis();
testMap1(map);
totalTime += System.currentTimeMillis() - startTime;
}
System.out.println("testMap1 avg time is :" + (totalTime / outSize));
// 省略其他方法,程式碼跟上面一致
}
為了避免一些干擾,這裡透過外層的 for
來進行多次計算,然後求平均值,當我們的引數分別是 outSize
= 1,mapSize
= 200 的時候,測試的結果如下
當隨著我們增大 mapSize
的時候,我們會發現,後面幾個方法的效能是逐漸上升的。
總結
從上面的例子來看,當我們的集合數量很少的時候,基本上普通的遍歷就可以搞定,不需要使用 JDK 8
的高階 API
來進行遍歷,當我們的集合數量較大的時候,就可以考慮採用 JDK 8
的 forEach
或者 Stream
來進行遍歷,這樣的話效率更高。在普通的遍歷方法中 entrySet()
的方法要比使用 keySet()
的方法好。