HashMap遍历方式性能比较

JDK8之前,可以使用ketSet或者entrySet来遍历HashMap,JDK8中引入了map.forEach来进行遍历

1. keySet和entrySet

1.1 基本用法

keySet:

Map<String,Object> map = new HashMap<>();
Iterator it = map.keySet().iterator();
Object key;
Object value;
while(it.hasNext()){
    key = it.next();
    value = map.get(key);//性能差异
    System.out.println(key+":"+value);
}

entrySet:

Map<String,Object> map = new HashMap<>();
Iterator it = map.entrySet().iterator();
Object key;
Object value;
while(it.hasNext()){
    Map.Entry entry = (Map.Entry)it.next();
    key = entry.getKey();
    value = entry.getValue();//性能差异
    System.out.println(key+":"+value);
}

1.2 源码分析

keySet:

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return 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();
            }
        }
    }

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();
            }
        }
    }

从源码中可以看出,当要得到某个value时,keySet需要从hashMap中get,entrySet相比keySet少了遍历table的过程,这就是两者性能上的主要差别

map.forEach()

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);
        }
    }

源码分析可知,map.foreach()本质上还是使用的entrySet

实例

package com.feiyu.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @author maokeluo
 * @desc 遍历hashMap性能比较
 * @create 17-9-2
 */
public class TraverseHsahMap {

    public static void main(String[] args) throws InterruptedException {
        Map<String, String> map = new HashMap<String, String>();
        int num = 10000000;
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < num; i++){
                    String temp = String.valueOf(i);
                    map.put(temp,temp);
                }
            }
        });
        thread.start();
        thread.join();
        
        //forEach map.entrySet
        long start1 = System.currentTimeMillis();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            entry.getKey();
            entry.getValue();
        }
        long end1 = System.currentTimeMillis() - start1;
        System.out.println("forEach map.entrySet遍历HashMap使用时间:"+end1);

        //显式调用map.entrySet()的集合迭代器
        long start2 = System.currentTimeMillis();
        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            entry.getKey();
            entry.getValue();
        }
        long end2 = System.currentTimeMillis() - start2;
        System.out.println("显式调用map.entrySet()的集合迭代器遍历HashMap使用时间:"+end2);

        //forEach map.keySet在调用get取值
        long start3 = System.currentTimeMillis();
        for (String key : map.keySet()) {
            map.get(key);
        }
        long end3 = System.currentTimeMillis() - start3;
        System.out.println("forEach map.keySet在调用get取值遍历HashMap使用时间:"+end3);

        //forEach map.entrySet(),用临时变量保存map.entrySet()
        long start4 = System.currentTimeMillis();
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        for (Map.Entry<String, String> entry : entrySet) {
            entry.getKey();
            entry.getValue();
        }
        long end4 = System.currentTimeMillis() - start4;
        System.out.println("forEach map.entrySet(),用临时变量保存map.entrySet()使用时间:"+end4);
    }
}

结果

| map size | 10000 | 100000 | 1000000 | 10000000 |
|——–|——–|
| forEach entrySet | 2ms | 11ms | 32ms | 199ms |
| for iterator entrySet| 1ms | 5ms | 29ms | 203ms |
| forEach keySet | 2ms | 7ms | 54ms | 273ms |
| for entrySet=entrySet()| 1ms | 5ms | 28ms | 189ms |

遍历方式结论总结

  • hashMap的循环遍历,如果不仅需要key又需要value,使用
for (Map.Entry<String, String> entry : map.entrySet()) {
            entry.getKey();
            entry.getValue();
        }

效率更高

  • 如果只是遍历key不需要value,可以直接使用
for (String key : map.keySet()) {
            //操作key 
        }
    原文作者:fantasticMao
    原文地址: https://www.jianshu.com/p/379b827787f5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞