Java源碼解析HashMap的keySet()方法
HashMap的keySet()
方法比較簡(jiǎn)單,作用是獲取HashMap中的key的集合。雖然這個(gè)方法十分簡(jiǎn)單,似乎沒(méi)有什么可供分析的,但真正看了源碼,發(fā)現(xiàn)自己還是有很多不懂的地方。下面是keySet的代碼。
public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; }
從代碼中了解到,第一次調(diào)用keySet方法時(shí),keySet屬性是null,然后進(jìn)行了初始化,再將keySet屬性返回。也就是說(shuō),HashMap里并不會(huì)隨著put和remove的進(jìn)行也維護(hù)一個(gè)keySet集合,而是在第一次調(diào)用keySet方法時(shí),才給keySet屬性初始化。
按照自己以往的理解,以為keySet返回的是一個(gè)集合,集合里面保存了HashMap的所有的Key。因?yàn)橛辛酥邢热霝橹鞯挠∠?,所以讀源碼時(shí),才感覺(jué)源碼很奇怪。從源碼中可以看到,初始化時(shí),只是創(chuàng)建了一個(gè)KeySet類(lèi)的對(duì)象,并沒(méi)有把HashMap的key都加入進(jìn)來(lái),方法就返回了。除了自己以往的理解外,還有一個(gè)現(xiàn)象,讓我堅(jiān)信這時(shí)HashMap的key已經(jīng)加入到keySet了,那就是在調(diào)試代碼過(guò)程中IDE給出的調(diào)試信息。如下圖。從圖中可以看出,創(chuàng)建完成KeySet()后,調(diào)試信息就已經(jīng)可以顯示出,ks中有2個(gè)元素了。這個(gè)信息更加堅(jiān)定了自己之前的理解。
那么,HashMap的key是什么時(shí)候加入到keySet集合中的呢?順著這個(gè)思路,我進(jìn)行了一步一步的分析。自己看了KeySet類(lèi)的構(gòu)造函數(shù),發(fā)現(xiàn)只有默認(rèn)構(gòu)造函數(shù)。那么我想,如果沒(méi)有在KeySet構(gòu)造函數(shù)里把HashMap的key加入進(jìn)來(lái),那么就有可能是在KeySet的父類(lèi)的構(gòu)造函數(shù)中加入進(jìn)來(lái)的。然后,自己找遍了KeySet類(lèi)的父類(lèi)的構(gòu)造函數(shù),發(fā)現(xiàn)都是空實(shí)現(xiàn),并沒(méi)有任何加入HashMap的key的操作。這到底是怎么回事呢?
其實(shí)HashMap的key并沒(méi)有加入到keySet集合中,而是在遍歷的時(shí)候,使用迭代器對(duì)key進(jìn)行的遍歷。這是結(jié)論。下面我們看一下原因和過(guò)程。
首先看一下KeySet類(lèi)的代碼,如下圖。可以看到,KeySet類(lèi)中的迭代器函數(shù),返回的是一個(gè)KeyIterator類(lèi)的對(duì)象。它的next方法返回的是HashIterator的nextNode的key。也就是說(shuō),當(dāng)使用迭代器遍歷set內(nèi)的元素時(shí),KeySet類(lèi)的迭代器,會(huì)保證能夠依次獲取到HashMap的節(jié)點(diǎn)的key值,這就是我們遍歷keySet的過(guò)程的實(shí)質(zhì)。
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(); } } } final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } 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; } }
那么,這里我們可以思考這么一個(gè)問(wèn)題。通過(guò)HashMap的keySet獲取到keySet后,難道只能用迭代器遍歷嗎?keySet方法不把HashMap的key都加入到set中,那么調(diào)用者使用for(int i = 0; i < size; i ++)的方式遍歷時(shí),豈不是無(wú)法遍歷set中的key了嗎?是的,確實(shí)是的。keySet確實(shí)沒(méi)有把key加入到set中,另外,它不用擔(dān)心調(diào)用者用for(int i = 0; i < size; i ++)的方式遍歷時(shí)獲取不到key,因?yàn)閟et根本就沒(méi)有set.get(i)這樣類(lèi)似的方法,要想遍歷set,只能用迭代器,或者使用foreach方式(本質(zhì)還是迭代器)。
這里還有個(gè)問(wèn)題需要解釋,就是在調(diào)試代碼時(shí),既然key沒(méi)有加入到set中,那么IDE如何顯示出set中有2個(gè)元素這樣的信息的?原來(lái),IDE顯示對(duì)象信息時(shí),會(huì)調(diào)用對(duì)象的toString方法。而集合的toString方法就是顯示出集合中的元素個(gè)數(shù)。
這里再思考一步,如果我們?cè)诩系膖oString方法加上斷點(diǎn),那么IDE顯示對(duì)象信息時(shí),會(huì)不先停下來(lái)?答案是看情況。記得早些年間使用eclipse調(diào)試代碼時(shí),在toString方法加上斷點(diǎn)后,顯示對(duì)象信息時(shí)確實(shí)會(huì)停下來(lái)。然而我現(xiàn)在使用的是IDE是idea,idea在這一點(diǎn)上做了優(yōu)化。如果是IDE顯示對(duì)象信息調(diào)用的toString方法,那么toString方法的斷點(diǎn)會(huì)被跳過(guò),即不生效,但會(huì)給出一條提示信息,如下圖。如果程序員主動(dòng)調(diào)用對(duì)象的toString方法,那么,toString方法的斷點(diǎn)會(huì)生效,可以正常斷點(diǎn)調(diào)試。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
springboot使用jasypt對(duì)配置文件加密加密數(shù)據(jù)庫(kù)連接的操作代碼
這篇文章主要介紹了springboot使用jasypt對(duì)配置文件加密加密數(shù)據(jù)庫(kù)連接的操作代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Java反射機(jī)制,反射相關(guān)API,反射API使用方式(反射獲取實(shí)體類(lèi)字段名和注解值)
這篇文章主要介紹了Java反射機(jī)制,反射相關(guān)API,反射API使用方式(反射獲取實(shí)體類(lèi)字段名和注解值),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07Java模擬多線程實(shí)現(xiàn)搶票代碼實(shí)例
這篇文章主要介紹了Java模擬多線程實(shí)現(xiàn)搶票,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Java實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Java 中Timer和TimerTask 定時(shí)器和定時(shí)任務(wù)使用的例子
這篇文章主要介紹了Java 中Timer和TimerTask 定時(shí)器和定時(shí)任務(wù)使用的例子,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05徹底搞明白Spring中的自動(dòng)裝配和Autowired注解的使用
這篇文章主要介紹了徹底搞明白Spring中的自動(dòng)裝配和Autowired注解的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03學(xué)習(xí)不同 Java.net 語(yǔ)言中類(lèi)似的函數(shù)結(jié)構(gòu)
這篇文章主要介紹了學(xué)習(xí)不同 Java.net 語(yǔ)言中類(lèi)似的函數(shù)結(jié)構(gòu),函數(shù)式編程語(yǔ)言包含多個(gè)系列的常見(jiàn)函數(shù)。但開(kāi)發(fā)人員有時(shí)很難在語(yǔ)言之間進(jìn)行切換,因?yàn)槭煜さ暮瘮?shù)具有不熟悉的名稱。函數(shù)式語(yǔ)言傾向于基于函數(shù)范例來(lái)命名這些常見(jiàn)函數(shù)。,需要的朋友可以參考下2019-06-06spring boot如何使用AOP統(tǒng)一處理web請(qǐng)求
這篇文章主要介紹了spring boot如何使用AOP統(tǒng)一處理web請(qǐng)求,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12