如果我有一个用Java实现Map接口的对象,并且我希望对其中包含的每一对进行迭代,那么最有效的方法是什么?

元素的顺序是否取决于我对接口的特定映射实现?


当前回答

为了总结其他答案并将它们与我所知道的结合起来,我找到了10种主要的方法(见下文)。此外,我还编写了一些性能测试(见下面的结果)。例如,如果我们想找到映射的所有键和值的总和,我们可以写:

使用迭代器和Map.Entry长i=0;迭代器<Map.Entry<Integer,Integer>>it=Map.entrySet().iiterator();while(it.hasNext()){Map.Entry<Integer,Integer>pair=it.next();i+=pair.getKey()+pair.getValue();}使用foreach和Map.Entry长i=0;for(Map.Entry<Integer,Integer>对:Map.entrySet()){i+=pair.getKey()+pair.getValue();}使用Java 8中的forEach最终长[]i={0};map.forEach((k,v)->i[0]+=k+v);使用keySet和foreach长i=0;for(整数键:map.keySet()){i+=键+map.get(键);}使用keySet和迭代器长i=0;Iterator<Integer>itr2=map.keySet().Iterator();而(itr2.hasNext()){整数键=itr2.next();i+=键+map.get(键);}使用for和Map.Entry长i=0;for(Iterator<Map.Entry<Integer,Integer>>条目=Map.entrySet().Iterator();entries.hasNext();){Map.Entry<Integer,Integer>Entry=entries.next();i+=entry.getKey()+entry.getValue();}使用Java 8流API最终长[]i={0};map.entrySet().stream().forEach(e->i[0]+=e.getKey()+e.getValue());并行使用Java 8流API最终长[]i={0};map.entrySet().stream().allel().forEach(e->i[0]+=e.getKey()+e.getValue());使用Apache集合的IterableMap长i=0;MapIterator<Integer,Integer>it=iterableMap.MapIterator();while(it.hasNext()){i+=it.next()+it.getValue();}使用Eclipse(CS)集合的MutableMap最终长[]i={0};mutableMap.forEachKeyValue((key,value)->{i[0]+=键+值;});

性能测试(模式=平均时间,系统=Windows 8.1 64位,Intel i7-4790 3.60 GHz,16 GB)

对于小地图(100个元素),得分0.308是最好的基准模式控制分数误差单位测试3_UsingForEachAndJava8平均值10 0.308±0.021µs/op测试10_UsingEclipseMap平均值10 0.309±0.009µs/optest1_UsingWhileAndMapEntry平均值10 0.380±0.014µs/optest6_SingingForAndIterator平均值10 0.387±0.016µs/optest2_UsingForEachAndMapEntry平均值10 0.391±0.023µs/op测试7_UsingJava8StreamApi平均值10 0.510±0.014µs/optest9_UsingApacheIterableMap平均值10 0.524±0.008µs/optest4_UsingKeySetAndForEach平均值10 0.816±0.026µs/optest5_UsingKeySetAndIterator平均值10 0.863±0.025µs/optest8_UsingJava8StreamApiParallel平均值10 5.552±0.185µs/op对于包含10000个元素的地图,得分37.606是最好的基准模式控制分数误差单位测试10_UsingEclipseMap平均值10 37.606±0.790µs/op测试3_使用EachAndJava8平均值10 50.368±0.887µs/op测试6_SingingForAndIterator平均值10 50.332±0.507µs/optest2_UsingForEachAndMapEntry平均值10 51.406±1.032µs/op测试1_UsingWhileAndMapEntry平均值10 52.538±2.431µs/op测试7_UsingJava8StreamApi平均值10 54.464±0.712µs/optest4_UsingKeySetAndForEach平均值10 79.016±25.345µs/optest5_UsingKeySetAndIterator平均值10 91.105±10.220µs/optest8_UsingJava8StreamApiParallel平均值10 112.511±0.365µs/optest9_UsingApacheIterableMap平均值10 125.714±1.935µs/op对于包含100000个元素的地图,得分1184.767是最好的基准模式控制分数误差单位测试1_UsingWhileAndMapEntry平均值10 1184.767±332.968µs/op测试10_UsingEclipseMap平均值10 1191.735±304.273µs/optest2_UsingForEachAndMapEntry平均值10 1205.815±366.043µs/op测试6_SingingForAndIterator平均值10 1206.873±367.272µs/optest8_UsingJava8StreamApiParallel平均值10 1485.895±233.143µs/optest5_UsingKeySetAndIterator平均值10 1540.281±357.497µs/optest4_UsingKeySetAndForEach平均值10 1593.342±294.417µs/op测试3_UsingForEachAndJava8平均值10 1666.296±126.443µs/op测试7_UsingJava8StreamApi平均值10 1706.676±436.867µs/optest9_UsingApacheIterableMap平均值10 3289.866±1445.564µs/op

图表(性能测试取决于地图大小)

表(性能测试取决于地图大小)

          100     600      1100     1600     2100
test10    0.333    1.631    2.752    5.937    8.024
test3     0.309    1.971    4.147    8.147   10.473
test6     0.372    2.190    4.470    8.322   10.531
test1     0.405    2.237    4.616    8.645   10.707
test2     0.376    2.267    4.809    8.403   10.910
test7     0.473    2.448    5.668    9.790   12.125
test9     0.565    2.830    5.952   13.220   16.965
test4     0.808    5.012    8.813   13.939   17.407
test5     0.810    5.104    8.533   14.064   17.422
test8     5.173   12.499   17.351   24.671   30.403

所有测试都在GitHub上。

其他回答

使用迭代器和泛型的示例:

Iterator<Map.Entry<String, String>> entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Map.Entry<String, String> entry = entries.next();
  String key = entry.getKey();
  String value = entry.getValue();
  // ...
}

如果您通过Map进行迭代的原因是对值执行操作并写入结果Map。我建议在GoogleGuavaMaps类中使用转换方法。

import com.google.common.collect.Maps;

将地图添加到导入后,可以在地图上使用Maps.transformValues和Maps.transform Entries,如下所示:

public void transformMap(){
    Map<String, Integer> map = new HashMap<>();
    map.put("a", 2);
    map.put("b", 4);

    Map<String, Integer> result = Maps.transformValues(map, num -> num * 2);
    result.forEach((key, val) -> print(key, Integer.toString(val)));
    // key=a,value=4
    // key=b,value=8

    Map<String, String> result2 = Maps.transformEntries(map, (key, value) -> value + "[" + key + "]");
    result2.forEach(this::print);
    // key=a,value=2[a]
    // key=b,value=4[b]
}

private void print(String key, String val){
    System.out.println("key=" + key + ",value=" + val);
}

在地图上迭代的典型代码是:

Map<String,Thing> map = ...;
for (Map.Entry<String,Thing> entry : map.entrySet()) {
    String key = entry.getKey();
    Thing thing = entry.getValue();
    ...
}

HashMap是规范映射实现,不做任何保证(或者,如果不对其执行任何变异操作,则不应更改顺序)。SortedMap将根据键的自然顺序或Comparator(如果提供)返回条目。LinkedHashMap将按照插入顺序或访问顺序返回条目,具体取决于它的构造方式。EnumMap以键的自然顺序返回条目。

(更新:我认为这不再是真的。)注意,IdentityHashMap entrySet迭代器目前有一个特殊的实现,它为entrySet中的每个项返回相同的Map.Entry实例!然而,每次新迭代器推进Map.Entry时都会更新。

正确的方法是使用公认的答案,因为它是最有效的。我发现下面的代码看起来有点干净。

for (String key: map.keySet()) {
   System.out.println(key + "/" + map.get(key));
}

这里有一个泛型类型安全方法,可以调用它来转储任何给定的Map。

import java.util.Iterator;
import java.util.Map;

public class MapUtils {
    static interface ItemCallback<K, V> {
        void handler(K key, V value, Map<K, V> map);
    }

    public static <K, V> void forEach(Map<K, V> map, ItemCallback<K, V> callback) {
        Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();

        while (it.hasNext()) {
            Map.Entry<K, V> entry = it.next();

            callback.handler(entry.getKey(), entry.getValue(), map);
        }
    }

    public static <K, V> void printMap(Map<K, V> map) {
        forEach(map, new ItemCallback<K, V>() {
            @Override
            public void handler(K key, V value, Map<K, V> map) {
                System.out.println(key + " = " + value);
            }
        });
    }
}

实例

下面是它的使用示例。请注意,Map的类型由该方法推断。

import java.util.*;

public class MapPrinter {
    public static void main(String[] args) {
        List<Map<?, ?>> maps = new ArrayList<Map<?, ?>>() {
            private static final long serialVersionUID = 1L;
            {
                add(new LinkedHashMap<String, Integer>() {
                    private static final long serialVersionUID = 1L;
                    {
                        put("One", 0);
                        put("Two", 1);
                        put("Three", 3);
                    }
                });

                add(new LinkedHashMap<String, Object>() {
                    private static final long serialVersionUID = 1L;
                    {
                        put("Object", new Object());
                        put("Integer", new Integer(0));
                        put("Double", new Double(0.0));
                    }
                });
            }
        };

        for (Map<?, ?> map : maps) {
            MapUtils.printMap(map);
            System.out.println();
        }
    }
}

输出

One = 0
Two = 1
Three = 3

Object = java.lang.Object@15db9742
Integer = 0
Double = 0.0