keySet 与entrySet 遍历HashMap性能差别

来源:互联网 发布:小米免费网络短信 编辑:程序博客网 时间:2024/05/21 21:36

     对于Java中Map的遍历方式,很多文章都推荐使用entrySet,认为其比keySet的效率高很多。理由是:entrySet方法一次拿到所有key和value的集合;而keySet拿到的只是key的集合,针对每个key,都要去Map中额外查找一次value,从而降低了总体效率。那么实际情况如何呢?

一.问题发现 


今天,在写完代码后用Find Bugs扫锚了一下,发现类中一处代码中有提示如下内容: 
        Java代码  
Map<String, EventChain> map = ContextHolder.getContext().getEventChains();   
        for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext();) {   
            String key = iter.next();   
            EventChain eventChain = eventChains.get(key);   
         }  

Map<String, EventChain> map = ContextHolder.getContext().getEventChains(); 
        for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext();) { 
            String key = iter.next(); 
            EventChain eventChain = eventChains.get(key); 
         } 
makes inefficient use of keySet iterator instead of entrySet iterator 
意思是说用keySet 方式遍历Map的性能不如entrySet性能好.起初想不明白,索性仔细看下代码: 

二.常用的遍历HashMap的两种方法 

1.第一种方式 
Java代码  
Iterator<String> keySetIterator = keySetMap.keySet().iterator();   
        while (keySetIterator.hasNext()) {   
            String key = keySetIterator.next();   
            String value = keySetMap.get(key);   
               
        }  

Iterator<String> keySetIterator = keySetMap.keySet().iterator(); 
  while (keySetIterator.hasNext()) { 
   String key = keySetIterator.next(); 
   String value = keySetMap.get(key); 
   
  } 


2.第二种方式 
Java代码  
Iterator<Entry<String, String>> entryKeyIterator = entrySetMap.entrySet()   
                .iterator();   
        while (entryKeyIterator.hasNext()) {   
            Entry<String, String> e = entryKeyIterator.next();   
            String value=e.getValue();   
        }  

Iterator<Entry<String, String>> entryKeyIterator = entrySetMap.entrySet() 
    .iterator(); 
  while (entryKeyIterator.hasNext()) { 
   Entry<String, String> e = entryKeyIterator.next(); 
   String value=e.getValue(); 
  } 

三.性能比较 

    到底第二种方式的性能比第一种方式的性能高多少呢,通过一个简单的测试类可以看一下,测试代码如下: 
Java代码  
public class HashMapTest {   
    public static void main(String[] args) {   
  
        HashMap<String, String> keySetMap = new HashMap<String, String>();   
        HashMap<String, String> entrySetMap = new HashMap<String, String>();   
  
        for (int i = 0; i < 1000; i++) {   
            keySetMap.put("" + i, "keySet");   
        }   
        for (int i = 0; i < 1000; i++) {   
            entrySetMap.put("" + i, "entrySet");   
        }   
  
        long startTimeOne = System.currentTimeMillis();   
        Iterator<String> keySetIterator = keySetMap.keySet().iterator();   
        while (keySetIterator.hasNext()) {   
            String key = keySetIterator.next();   
            String value = keySetMap.get(key);   
            System.out.println(value);   
        }   
  
        System.out.println("keyset spent times:"  
                + (System.currentTimeMillis() - startTimeOne));   
  
        long startTimeTwo = System.currentTimeMillis();   
  
        Iterator<Entry<String, String>> entryKeyIterator = entrySetMap   
                .entrySet().iterator();   
        while (entryKeyIterator.hasNext()) {   
            Entry<String, String> e = entryKeyIterator.next();   
            System.out.println(e.getValue());   
        }   
        System.out.println("entrySet spent times:"  
                + (System.currentTimeMillis() - startTimeTwo));   
  
    }   
}  

public class HashMapTest { 
public static void main(String[] args) { 

  HashMap<String, String> keySetMap = new HashMap<String, String>(); 
  HashMap<String, String> entrySetMap = new HashMap<String, String>(); 

  for (int i = 0; i < 1000; i++) { 
   keySetMap.put("" + i, "keySet"); 
  } 
  for (int i = 0; i < 1000; i++) { 
   entrySetMap.put("" + i, "entrySet"); 
  } 

  long startTimeOne = System.currentTimeMillis(); 
  Iterator<String> keySetIterator = keySetMap.keySet().iterator(); 
  while (keySetIterator.hasNext()) { 
   String key = keySetIterator.next(); 
   String value = keySetMap.get(key); 
   System.out.println(value); 
  } 

  System.out.println("keyset spent times:" 
    + (System.currentTimeMillis() - startTimeOne)); 

  long startTimeTwo = System.currentTimeMillis(); 

  Iterator<Entry<String, String>> entryKeyIterator = entrySetMap 
    .entrySet().iterator(); 
  while (entryKeyIterator.hasNext()) { 
   Entry<String, String> e = entryKeyIterator.next(); 
   System.out.println(e.getValue()); 
  } 
  System.out.println("entrySet spent times:" 
    + (System.currentTimeMillis() - startTimeTwo)); 




通过测试发现,第二种方式的性能通常要比第一种方式高一倍. 

四.原因分析: 

  通过查看源代码发现,调用这个方法keySetMap.keySet()会生成KeyIterator迭代器,其next方法只返回其key值. 
Java代码  
private class KeyIterator extends HashIterator<K> {   
       public K next() {   
           return nextEntry().getKey();   
       }   
   }  

private class KeyIterator extends HashIterator<K> { 
        public K next() { 
            return nextEntry().getKey(); 
        } 
    } 
而调用entrySetMap.entrySet()方法会生成EntryIterator 迭代器,其next方法返回一个Entry对象的一个实例,其中包含key和value. 
Java代码  
private class EntryIterator extends HashIterator<Map.Entry<K,V>> {   
       public Map.Entry<K,V> next() {   
           return nextEntry();   
       }   
}  

private class EntryIterator extends HashIterator<Map.Entry<K,V>> { 
        public Map.Entry<K,V> next() { 
            return nextEntry(); 
        } 
  } 

二者在此时的性能应该是相同的,但方式一再取得key所对应的value时,此时还要访问Map的这个方法,这时,方式一多遍历了一次table. 

Java代码  
public V get(Object key) {   
        Object k = maskNull(key);   
        int hash = hash(k);   
        int i = indexFor(hash, table.length);   
        Entry<K,V> e = table[i];    
        while (true) {   
            if (e == null)   
                return null;   
            if (e.hash == hash && eq(k, e.key))    
                return e.value;   
            e = e.next;   
        }   
    }  

public V get(Object key) { 
        Object k = maskNull(key); 
        int hash = hash(k); 
        int i = indexFor(hash, table.length); 
        Entry<K,V> e = table[i]; 
        while (true) { 
            if (e == null) 
                return null; 
            if (e.hash == hash && eq(k, e.key)) 
                return e.value; 
            e = e.next; 
        } 
    } 

这个方法就是二者性能差别的主要原因. 
0 0