本篇文章首先讲一下Map的几种遍历写法,然后从源码角度比较不同写法的性能差别。
方法一 entrySet()
public static void func1(Map<Integer,Integer> map) {
for(Map.Entry<Integer,Integer> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
大多情况下采用上面这种方式,因为通过遍历Entry,可以同时获取键值。
方法二 keySet()、values()
public static void func2(Map<Integer,Integer> map) {
for (Integer key : map.keySet()){
System.out.println(key);
System.out.println(map.get(key));
}
}
keySet()方法是对Map的key值进行遍历,我们可以通过找到的key值,使用get(key)方法获取对应的value。也可以使用values()方法来获取所有的value,然后遍历value。具体代码如下:
public static void func3(Map<Integer,Integer> map) {
for(Integer value : map.values()) {
System.out.println(value);
}
}
方法三 Iterator
public static void fun4(Map<Integer,Integer> map) {
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while(entries.hasNext()){
Map.Entry<Integer, Integer> entry = entries.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
这种方法与第一种方法相比,显得有些冗余。多设置一个Iterator变量,然后通过它来进行遍历。但是这种写法有一个好处,就是在遍历的过程中可以对Iterator进行remove的操作,删除调用next()方法返回的元素。这种操作在其他两种方法中是不可行的。for-each下是不可以对集合元素进行删除操作的。不然会报错。
public static void main(String[] args){
Map<Integer,Integer> map = new HashMap<>();
map.put(1,1);
map.put(2,2);
map.put(3,3);
map.put(4,4);
//func1(map);
func4(map);
}
public static void func4(Map<Integer,Integer> map) {
System.out.println(map.size()); //4
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
Map.Entry<Integer, Integer> entry = entries.next(); //1->1
entries.remove();
System.out.println(map.size());//3
for(Integer value : map.values()) {
System.out.println(value); // 2,3,4
}
System.out.println(entry.getKey()); //1
}
方法四
Java8中,Map接口中提供了默认方法 forEach,可以用于map的遍历。关于Java8的语言新特性-接口默认方法可以参见前面的文章。
public static void func5(Map<Integer,Integer> map) {
map.forEach((k,v)->{
System.out.println(k);
System.out.println(v);
});
}
从源码可以看出Map.forEach()方法本质上还是entrySet()方法
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
下面我们从HashMap的源码分析三种遍历方式的性能
entrySet()
HashMap中entrySet()方法如下,重写Map中的entrySet()方法
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> 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);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
keySet()
public Set<K> keySet() {
Set<K> ks;
return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}
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();
}
}
}
上面的方法二使用keySet()方法得到key的所有值,然后再使用get(key)获取对应的value,这样的方式比entrySet()方法一次性获取key和value耗时要增加一倍,相当于遍历两次map集合,一次遍历获得key,一次遍历获得value。
总结
JDK8之前,可以使用keySet或者entrySet来遍历HashMap,JDK8中引入了map.foreach来进行遍历。
原因:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。
参考:https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/iterate-through-a-hashmap.md
http://www.importnew.com/26298.html