解讀為什么不推薦使用keySet()進(jìn)行遍歷HashMap
不推薦使用keySet()進(jìn)行遍歷HashMap
HashMap相信所有學(xué)Java的都一定不會(huì)感到陌生,作為一個(gè)非常重用且非常實(shí)用的Java提供的容器,它在我們的代碼里面隨處可見。因此遍歷操作也是我們經(jīng)常會(huì)使用到的。
HashMap的遍歷方式現(xiàn)如今有非常多種:
- 使用迭代器(Iterator)。
- 使用
keySet()
獲取鍵的集合,然后通過增強(qiáng)的 for 循環(huán)遍歷鍵。 - 使用
entrySet()
獲取鍵值對的集合,然后通過增強(qiáng)的 for 循環(huán)遍歷鍵值對。 - 使用 Java 8+ 的 Lambda 表達(dá)式和流。
以上遍歷方式的孰優(yōu)孰劣,在《阿里巴巴開發(fā)手冊》中寫道:
這里推薦使用的是entrySet
進(jìn)行遍歷,在Java8中推薦使用Map.forEach()
。給出的理由是遍歷次數(shù) 上的不同。
- keySet遍歷,需要經(jīng)過兩次 遍歷。
- entrySet遍歷,只需要一次 遍歷。
其中keySet遍歷了兩次,一次是轉(zhuǎn)為Iterator對象,另一次是從hashMap中取出key所對應(yīng)的value。
其中后面一段話很好理解,但是前面這句話卻有點(diǎn)繞,為什么轉(zhuǎn)換成了Iterator遍歷了一次?
我查閱了各個(gè)平臺(tái)對HashMap的遍歷,其中都沒有或者原封不動(dòng)的照搬上句話。(當(dāng)然也可能是我沒有查閱到靠譜的文章,歡迎指正)
keySet如何遍歷了兩次
我們首先寫一段代碼,使用keySet遍歷Map。
public class Test { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); for (String key : map.keySet()) { String value = map.get(key); System.out.println(key + ":" + value); } } }
運(yùn)行結(jié)果顯而易見的是
k1:v1
k2:v2
k3:v3
兩次遍歷,第一次遍歷所描述的是轉(zhuǎn)為Iterator對象我們好像沒有從代碼中看見,我們看到的后面所描述的遍歷,也就是遍歷map,keySet()
所返回的Set
集合中的key,然后去HashMap中拿取value的。
Iterator對象呢?如何遍歷轉(zhuǎn)換為Iterator對象的呢?
首先我們這種遍歷方式大家都應(yīng)該知道是叫:增強(qiáng)for循環(huán),for-each
這是一種Java的語法糖~。
我們可以通過反編譯,或者直接通過Idea在class文件中查看對應(yīng)的Class文件
public class Test { public Test() { } public static void main(String[] args) { Map<String, String> map = new HashMap(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); Iterator var2 = map.keySet().iterator(); while(var2.hasNext()) { String key = (String)var2.next(); String value = (String)map.get(key); System.out.println(key + ":" + value); } } }
和我們編寫的是存在差異的,其中我們可以看到其中通過map.keySet().iterator()
獲取到了我們所需要看見的Iterator
對象。
那么它又是怎么轉(zhuǎn)換成的呢?為什么需要遍歷呢?我們查看iterator()
方法
1 iterator()
發(fā)現(xiàn)是Set定義的一個(gè)接口。返回此集合中元素的迭代器
2 HashMap.KeySet#iterator()
我們查看HashMap中keySet類對該方法的實(shí)現(xiàn)。
final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
其中的iterator()方法返回的是一個(gè)KeyIterator
對象,那么究竟是在哪里進(jìn)行了遍歷呢?我們接著往下看去。
3 HashMap.KeyIterator
final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } }
這個(gè)類也很簡單:
- 繼承了
HashIterator
類。 - 實(shí)現(xiàn)了
Iterator
接口。 - 一個(gè)
next()
方法。
還是沒有看見哪里進(jìn)行了遍歷,那么我們繼續(xù)查看HashIterator
類
4 HashMap.HashIterator
abstract class HashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }
我們可以發(fā)現(xiàn)這個(gè)構(gòu)造器中存在了一個(gè)do-while
循環(huán)操作,目的是找到一個(gè)第一個(gè)不為空的entry
。
HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } }
而KeyIterator
是extendHashIterator
對象的。這里涉及到了繼承的相關(guān)概念,大家忘記的可以找相關(guān)的文章看看,或者我也可以寫一篇~~dog。
例如兩個(gè)類
public class Father { public Father(){ System.out.println("father"); } } public class Son extends Father{ public static void main(String[] args) { Son son = new Son(); } }
創(chuàng)建Son對象的同時(shí),會(huì)執(zhí)行Father構(gòu)造器。也就會(huì)打印出father
這句話。
那么這個(gè)循環(huán)操作就是我們要找的循環(huán)操作了。
總結(jié)
使用keySet遍歷,其實(shí)內(nèi)部是使用了對應(yīng)的iterator()
方法。
iterator()
方法是創(chuàng)建了一個(gè)KeyIterator
對象。KeyIterator
對象extendHashIterator
對象。HashIterator
對象的構(gòu)造方法中,會(huì)遍歷找到第一個(gè)不為空的entry
。
keySet->iterator()->KeyIterator->HashIterator
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java8如何通過poi+text將word轉(zhuǎn)為pdf
這篇文章主要介紹了java8如何通過poi+text將word轉(zhuǎn)為pdf問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04Java多態(tài)和實(shí)現(xiàn)接口的類的對象賦值給接口引用的方法(推薦)
下面小編就為大家?guī)硪黄狫ava多態(tài)和實(shí)現(xiàn)接口的類的對象賦值給接口引用的方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02SpringBoot項(xiàng)目接入MQTT的詳細(xì)指南
MQTT是一種輕量級的消息傳輸協(xié)議,特別適用于物聯(lián)網(wǎng)(IoT)場景,具有低帶寬、高延遲網(wǎng)絡(luò)環(huán)境下的優(yōu)勢,SpringBoot作為流行的 Java開發(fā)框架,能夠方便地與MQTT集成,實(shí)現(xiàn)高效的消息通信,本文將詳細(xì)介紹如何在SpringBoot項(xiàng)目中接入MQTT,需要的朋友可以參考下2025-03-03java實(shí)現(xiàn)客戶端向服務(wù)器發(fā)送文件
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)客戶端向服務(wù)器發(fā)送文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01springBoot不同module之間互相依賴的實(shí)現(xiàn)
本文主要介紹了springBoot不同module之間互相依賴的實(shí)現(xiàn),不同模塊之間的依賴通常是通過Maven或Gradle來管理的,下面就來介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下2024-08-08scala當(dāng)中的文件操作和網(wǎng)絡(luò)請求的實(shí)現(xiàn)方法
這篇文章主要介紹了scala當(dāng)中的文件操作和網(wǎng)絡(luò)請求的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06