HashMap中keySet和entrySet的區別

語言: CN / TW / HK

theme: cyanosis

今天進行CR的時候,發現程式碼中對HashMap進行遍歷時,使用的是for(key in map.keys),就想起了之前看到的一個文章,說使用entrySet()進行遍歷效能更高。當時沒有追究,但是最近看《程式碼整潔之道》,學到了

勒布朗(LeBlanc)法則:稍後等於永不(Later equals never)

那麼今天一定要把其中的原理搞清楚。

用法

  • keySet() for (key in map.keys) { map[key] }

  • entrySet() for (entry in map.entries) { entry.value }

差異

  • keySet()返回的是一個關於K的Set (第一次查詢),要獲取value,還需要通過map[key]獲取 (第二步查詢)

  • entrySet()返回的是一個K,V鍵值對的Set (第一次查詢),獲取value直接呼叫entry.value (不需要查詢)

思考

  1. 雖然使用entrySet()進行遍歷,效能更好,但是多執行緒下就沒辦法保證安全性了,所以在多執行緒情況下,建議考慮使用ConcurrentHashMap()
  2. 觀察entrySet()方法,發現entrySetnull時,返回的是EntrySet()物件,但是看EntrySet原始碼,構造方法是空實現,那資料從哪裡來的呢?先看下原始碼:

``` public Set> entrySet() { Set> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; }

final class EntrySet extends AbstractSet> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } // 這裡返回了EntryIterator() public final Iterator> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry<?, ?> e)) return false; Object key = e.getKey(); Node candidate = getNode(key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry<?, ?> e) { Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator> spliterator() { return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry> action) { Node[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (Node e : tab) { for (; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } } `` 可以發現iterator()方法返回了EntryIterator()物件,深入其中,發現其繼承了HashIterator,而HashIterator構造方法則是從關聯到了table,在HashMap的put方法中,就是向table`中插入值。

可能有人會問,這與EntrySet()有什麼關係呢?for迴圈怎麼獲取到的Set物件呢?

通過對kotlin程式碼轉成Bytecode,再反編譯成java語言,可以發現: ``` var3 = map.entrySet().iterator();

while(var3.hasNext()) { Map.Entry entry = (Map.Entry)var3.next(); entry.getValue(); } `` 就是說for (entry in map.entries),實際上是遍歷的迭代器,這就與前面EntrySetiterator()`方法的返回值關聯起來了,突然想起了名偵探柯南的畫面,哈哈哈

名偵探柯南.jpeg