标签:
Java 的 HashMap 是使用频率非常高的一个类,在工作中经常会遇到遍历 HashMap 的场景,那么如何高效地遍历 HashMap 呢?首先从源码分析 HashMap 的存储结构:
1 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
可以看出实际存储的结构是一个 entry 数组,一般遍历 HashMap 主要有两种方法,通过获得 map 的 entry set 或者 key set 的 iterator 进行遍历。依然从源码进行分析,看 HashMap 的 entry set 和 key set 是如何实现的。
1. Entry Set 的实现
1 private transient Set<Map.Entry<K,V>> entrySet = null; 2 3 private final class EntrySet extends AbstractSet<Map.Entry<K, V>> { 4 public Iterator<Map.Entry<K, V>> iterator() { 5 return newEntryIterator(); 6 } 7 8 public boolean contains(Object o) { 9 if (!(o instanceof Map.Entry)) { 10 return false; 11 } 12 13 Map.Entry<K, V> e = (Map.Entry<K, V>) o; 14 Entry<K, V> candidate = getEntry(e.getKey()); 15 16 return (candidate != null) && candidate.equals(e); 17 } 18 19 public boolean remove(Object o) { 20 return removeMapping(o) != null; 21 } 22 23 public int size() { 24 return size; 25 } 26 27 public void clear() { 28 HashMap.this.clear(); 29 } 30 }
从上述代码可以看出,如果需要遍历 entry set,只能通过 iterator 遍历,看一下 newEntryIterator() 方法的调用路径
1 Iterator<Map.Entry<K,V>> newEntryIterator() { 2 return new EntryIterator(); 3 } 4 5 private final class EntryIterator extends HashIterator<Map.Entry<K, V>> { 6 public Map.Entry<K, V> next() { 7 return nextEntry(); 8 } 9 } 10 11 final Entry<K,V> nextEntry() { 12 if (modCount != expectedModCount) 13 throw new ConcurrentModificationException(); 14 Entry<K,V> e = next; 15 if (e == null) 16 throw new NoSuchElementException(); 17 18 if ((next = e.next) == null) { 19 Entry[] t = table; 20 while (index < t.length && (next = t[index++]) == null); 21 } 22 current = e; 23 return e; 24 }
从 nextEntry() 方法中可以看出,通过 entry set 方法进行遍历,实际上是对 entry 数组的遍历,HashMap 不会单独地保存一个 entry set。
2. Key Set 的实现
同样通过源码分析 key set
1 transient volatile Set<K> keySet = null; // 来自 AbstractMap 2 3 private final class KeySet extends AbstractSet<K> { 4 public Iterator<K> iterator() { 5 return newKeyIterator(); 6 } 7 8 public int size() { 9 return size; 10 } 11 12 public boolean contains(Object o) { 13 return containsKey(o); 14 } 15 16 public boolean remove(Object o) { 17 return HashMap.this.removeEntryForKey(o) != null; 18 } 19 20 public void clear() { 21 HashMap.this.clear(); 22 } 23 } 24 25 Iterator<K> newKeyIterator() { 26 return new KeyIterator(); 27 } 28 29 private final class KeyIterator extends HashIterator<K> { 30 public K next() { 31 return nextEntry().getKey(); 32 } 33 }
遍历 key set 的方式,实际上也是通过遍历 entry set,再获得 entry 的 key 来实现的,因此如果通过 key set 遍历 HashMap,同 entry set 方式相比,会多出通过 HashMap.get() 获取 value 这一步骤,下面通过测试代码验证猜想。
1 import java.util.Calendar; 2 import java.util.HashMap; 3 import java.util.Map; 4 5 public class TestHashMap { 6 7 public static void traverse_hash_map(int type) { 8 HashMap<Integer, Integer> map = new HashMap(); 9 int size = 10000000; 10 for (int i = 0; i < size; ++i) { 11 map.put(i, i); 12 } 13 14 double start = Calendar.getInstance().getTimeInMillis(); 15 if (0 == type) { 16 traverse_hash_map_by_entry_set(map); 17 System.out.println("by entry set: "); 18 } else { 19 traverse_hash_map_by_key_set(map); 20 System.out.println("by key set: "); 21 } 22 System.out.println(Calendar.getInstance().getTimeInMillis() - start); 23 } 24 25 private static void traverse_hash_map_by_entry_set(HashMap<Integer, Integer> map) { 26 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { 27 int key = entry.getKey(); 28 int value = entry.getValue(); 29 } 30 } 31 32 private static void traverse_hash_map_by_key_set(HashMap<Integer, Integer> map) { 33 for (Integer key : map.keySet()) { 34 int value = map.get(key); 35 } 36 } 37 38 public static void main(String[] args) { 39 traverse_hash_map(0); 40 traverse_hash_map(1); 41 } 42 }
测试结果为:
1 by entry set: 2 112.0 3 by key set: 4 129.0
交换测试的顺序,结果为:
1 by key set: 2 157.0 3 by entry set: 4 95.0
以上测试说明,对于 HashMap 的遍历,通过 entry set 的效率高于 key set 的遍历,应该尽量使用 entry set iterator 的方法遍历整个 HashMap。
标签:
原文地址:http://www.cnblogs.com/uncleli1988/p/4495114.html