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

元素的顺序是否取决于接口的特定地图实现?

评论

在使用Lambda表达式的Java 8中:stackoverflow.com/a/25616206/1503859

Java 8:stackoverflow.com/questions/46898/…

#1 楼

Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + "/" + entry.getValue());
}


评论


如果这样做,则由于Entry是Map中的嵌套类,因此它将不起作用。 java.sun.com/javase/6/docs/api/java/util/Map.html

–ScArcher2
2010-3-22在13:30

您可以将导入写为“ import java.util.Map.Entry;”。它会工作。

– jjujuma
2010-4-30 10:34



@Pureferret您可能要使用迭代器的唯一原因是,如果需要调用其remove方法。如果是这种情况,另一个答案将向您展示如何执行此操作。否则,如上面答案中所示的增强循环是可行的方法。

–叙利亚
2012年10月8日上午10:34

我相信Map.Entry的形式比将内部类导入当前名称空间更清晰。

–乔西亚(Josiah Yoder)
2014年12月4日20:31

请注意,如果只想遍历值或键,则可以使用map.values()或map.keySet()。

– dguay
16-10-12在21:03



#2 楼

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



使用迭代器和Map.Entry

long i = 0;
Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<Integer, Integer> pair = it.next();
    i += pair.getKey() + pair.getValue();
}



使用foreach和Map.Entry

long i = 0;
for (Map.Entry<Integer, Integer> pair : map.entrySet()) {
    i += pair.getKey() + pair.getValue();
}



从Java 8使用forEach

final long[] i = {0};
map.forEach((k, v) -> i[0] += k + v);




使用keySet和foreach

long i = 0;
for (Integer key : map.keySet()) {
    i += key + map.get(key);
}



使用keySet和迭代器

long i = 0;
Iterator<Integer> itr2 = map.keySet().iterator();
while (itr2.hasNext()) {
    Integer key = itr2.next();
    i += key + map.get(key);
}



使用和Map.Entry

long i = 0;
for (Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator(); entries.hasNext(); ) {
    Map.Entry<Integer, Integer> entry = entries.next();
    i += entry.getKey() + entry.getValue();
}



使用Java 8 Stream API

final long[] i = {0};
map.entrySet().stream().forEach(e -> i[0] += e.getKey() + e.getValue());



使用Java 8 Stream API并行

final long[] i = {0};
map.entrySet().stream().parallel().forEach(e -> i[0] += e.getKey() + e.getValue());



使用Apache Collections的IterableMap

long i = 0;
MapIterator<Integer, Integer> it = iterableMap.mapIterator();
while (it.hasNext()) {
    i += it.next() + it.getValue();
}



使用Eclipse(CS)集合的MutableMap

final long[] i = {0};
mutableMap.forEachKeyValue((key, value) -> {
    i[0] += key + value;
});



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



小地图(100个元素),得分0.308是最佳

Benchmark                          Mode  Cnt  Score    Error  Units
test3_UsingForEachAndJava8         avgt  10   0.308 ±  0.021  µs/op
test10_UsingEclipseMap             avgt  10   0.309 ±  0.009  µs/op
test1_UsingWhileAndMapEntry        avgt  10   0.380 ±  0.014  µs/op
test6_UsingForAndIterator          avgt  10   0.387 ±  0.016  µs/op
test2_UsingForEachAndMapEntry      avgt  10   0.391 ±  0.023  µs/op
test7_UsingJava8StreamApi          avgt  10   0.510 ±  0.014  µs/op
test9_UsingApacheIterableMap       avgt  10   0.524 ±  0.008  µs/op
test4_UsingKeySetAndForEach        avgt  10   0.816 ±  0.026  µs/op
test5_UsingKeySetAndIterator       avgt  10   0.863 ±  0.025  µs/op
test8_UsingJava8StreamApiParallel  avgt  10   5.552 ±  0.185  µs/op



对于具有10000个元素的地图,得分37.606是最好的

Benchmark                           Mode   Cnt  Score      Error   Units
test10_UsingEclipseMap              avgt   10    37.606 ±   0.790  µs/op
test3_UsingForEachAndJava8          avgt   10    50.368 ±   0.887  µs/op
test6_UsingForAndIterator           avgt   10    50.332 ±   0.507  µs/op
test2_UsingForEachAndMapEntry       avgt   10    51.406 ±   1.032  µs/op
test1_UsingWhileAndMapEntry         avgt   10    52.538 ±   2.431  µs/op
test7_UsingJava8StreamApi           avgt   10    54.464 ±   0.712  µs/op
test4_UsingKeySetAndForEach         avgt   10    79.016 ±  25.345  µs/op
test5_UsingKeySetAndIterator        avgt   10    91.105 ±  10.220  µs/op
test8_UsingJava8StreamApiParallel   avgt   10   112.511 ±   0.365  µs/op
test9_UsingApacheIterableMap        avgt   10   125.714 ±   1.935  µs/op



对于包含100000个元素的地图,得分11​​84.767是最好的。

Benchmark                          Mode   Cnt  Score        Error    Units
test1_UsingWhileAndMapEntry        avgt   10   1184.767 ±   332.968  µs/op
test10_UsingEclipseMap             avgt   10   1191.735 ±   304.273  µs/op
test2_UsingForEachAndMapEntry      avgt   10   1205.815 ±   366.043  µs/op
test6_UsingForAndIterator          avgt   10   1206.873 ±   367.272  µs/op
test8_UsingJava8StreamApiParallel  avgt   10   1485.895 ±   233.143  µs/op
test5_UsingKeySetAndIterator       avgt   10   1540.281 ±   357.497  µs/op
test4_UsingKeySetAndForEach        avgt   10   1593.342 ±   294.417  µs/op
test3_UsingForEachAndJava8         avgt   10   1666.296 ±   126.443  µs/op
test7_UsingJava8StreamApi          avgt   10   1706.676 ±   436.867  µs/op
test9_UsingApacheIterableMap       avgt   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上。

评论


@Viacheslav:非常好的答案。只是想知道在您的基准测试中如何通过捕获lambda来阻碍Java8 api ...(例如long sum = 0; map.forEach(/ *累积在变量sum * /中);捕获long的总和,这可能比说来慢例如stream.mapToInt(/ * whatever * /)。sum当然,您不能总是避免捕获状态,但这可能是替补席上的合理补充。

– GPI
16年5月12日在11:53

您的8测试错误。它从不同的线程访问相同的变量而无需同步。更改为AtomicInteger以解决问题。

–talex
16/09/13在15:20

@ZhekaKozlov:看看令人难以置信的大错误值。考虑到x±e的测试结果意味着在xe到x + e的区间内有结果,因此最快的结果(1184.767±332.968)的范围是852至1518,而第二慢的结果(1706.676±436.867)在1270和2144,因此结果仍然有明显的重叠。现在来看最慢的结果3289.866±1445.564,这意味着在1844和4735之间会有差异,您知道这些测试结果毫无意义。

–霍尔格
17年3月17日在18:28



比较三个主要实现:HashMap,LinkedHashMap和TreeMap呢?

–蒂埃里(Thierry)
17年11月7日23:37

#1和#6完全相同。使用while vs. for循环并不是迭代的另一种技术。让我感到惊讶的是,它们在您的测试中会有如此差异-这表明测试没有与与您要测试的事物无关的外部因素正确隔离。

– ErikE
18年7月14日在19:04

#3 楼

在Java 8中,您可以使用新的lambdas功能来快速,干净地完成它:

 Map<String,String> map = new HashMap<>();
 map.put("SomeKey", "SomeValue");
 map.forEach( (k,v) -> [do something with key and value] );

 // such as
 map.forEach( (k,v) -> System.out.println("Key: " + k + ": Value: " + v));


kv的类型将由编译器推断出来,不需要即可使用Map.Entry

简单易用!

评论


根据您要对地图执行的操作,还可以在map.entrySet()。stream()docs.oracle.com/javase/8/docs/api/java/util/stream返回的条目上使用流API。 /Stream.html

– Vitalii Fedorenko
2014年6月28日12:46



如果您想从forEach()内部引用在lambda表达式外部声明的非最终变量,则此方法将无效。

–克里斯
17年4月20日在20:29

@克里斯正确。如果您尝试从lambda外部有效地使用非最终变量,则此方法将无效。

–协调员
17年4月21日在21:44

#4 楼

是的,顺序取决于特定的Map实现。

@ ScArcher2具有更优雅的Java 1.5语法。在1.4中,我会做这样的事情:

Iterator entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Entry thisEntry = (Entry) entries.next();
  Object key = thisEntry.getKey();
  Object value = thisEntry.getValue();
  // ...
}


评论


首选for循环,而不是while .. for(迭代器entrys = myMap.entrySet()。iterator(); entry.hasNext();){...}使用此语法,“ entries”范围缩小为仅for循环。

–斋
09-10-20在13:20

@jpredham你是对的,因为使用for构造(项e:myMap.entrySet)不会允许您修改集合,但是@HanuAthena提到的示例应该可以工作,因为它为您提供了范围内的Iterator。 (除非我想念什么...)

– pkaeding
2012年1月10日15:42

IntelliJ给我关于Entry thisEntry =(Entry)entry.next();的错误:无法识别Entry。该伪代码是否还包含其他内容?

– JohnK
2015年1月10日,0:06



@JohnK尝试导入java.util.Map.Entry。

– pkaeding
2015年1月14日下午2:35

如果您具有整数键和字符串键,则此解决方案将不起作用。

–user5778069
17年1月21日在8:09

#5 楼

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

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迭代器当前具有一个特殊的实现,该实现为q中的每个项目返回相同的Map.Entry实例。 entrySet!但是,每次新的迭代器前进时,都会更新Map.Entry

评论


EnumMap和IdentityHashMap也具有这种特殊的行为

– Premraj
11 Mar 10 '11在15:41

“ LinkedHashMap会以访问顺序[...]返回条目。” ...那么您以访问元素的顺序来访问元素?是重言式的,还是一些有趣的东西,可能会使用题外话。 ;-)

– jpaugh
16年1月26日在20:41

@jpaugh仅直接访问LinkedHashMap计数。那些通过迭代器,拆分器,entrySet等进行的操作不会修改顺序。

– Tom Hawtin-大头钉
16年1月26日在21:24

1.虽然→如果? 2.最后一段可能会从复习中受益。

– Peter Mortensen
18年2月6日在22:36

#6 楼

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

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


评论


您应该将Iterator放入for循环中以限制其范围。

–郭富城
2012年2月17日在20:32

@SteveKuo“限制其范围”是什么意思?

– StudioWorks
2015年2月3日在16:13

@StudioWorks用于(Iterator >条目= myMap.entrySet()。iterator(); entry.hasNext();){Map.Entry 条目= entry.next() ; }。通过使用该构造,我们将(变量的可见性)条目的范围限制为for循环。

– ComFreek
15年3月13日在16:33

@ComFreek哦,我知道了。不知道那有多重要。

– StudioWorks
15年3月13日在16:59

#7 楼

这是一个分为两个部分的问题:

如何遍历Map的条目-@ ScArcher2完美地回答了这一问题。

迭代的顺序是什么-如果您是仅使用Map,严格来说,没有订购保证。因此,您不应真正依赖任何实现所给出的顺序。但是,SortedMap接口扩展了Map并提供了您所要的内容-实现将给出一致的排序顺序。

NavigableMap是另一个有用的扩展-这是SortedMap,它具有用于通过以下方式查找条目的其他方法它们在键集中的顺序位置。因此,这有可能首先消除了迭代的需要-使用entryhigherEntrylowerEntryceilingEntry方法之后,您可能能够找到要使用的特定floorEntrydescendingMap方法甚至为您提供了一种反转遍历顺序的显式方法。

#8 楼

有几种方法可以遍历map。

这里比较存储在map中的通用数据集的性能,方法是在map中存储一百万个键值对,然后遍历map。

1)对每个循环使用entrySet()

for (Map.Entry<String,Integer> entry : testMap.entrySet()) {
    entry.getKey();
    entry.getValue();
}


50毫秒

2)对每个循环使用keySet() in

for (String key : testMap.keySet()) {
    testMap.get(key);
}


76毫秒

3)使用entrySet()和迭代器

Iterator<Map.Entry<String,Integer>> itr1 = testMap.entrySet().iterator();
while(itr1.hasNext()) {
    Map.Entry<String,Integer> entry = itr1.next();
    entry.getKey();
    entry.getValue();
}


50毫秒

4)使用keySet()和迭代器

Iterator itr2 = testMap.keySet().iterator();
while(itr2.hasNext()) {
    String key = itr2.next();
    testMap.get(key);
}


75毫秒

我已引用this link

评论


运行时间来自本文,该文章未使用Java Microbenchmarking Harness。因此,时间是不可靠的,因为例如JIT编译器可以完全优化代码。

– AlexB
19年1月14日在13:44

#9 楼

正确的方法是使用公认的答案,因为它是最有效的。我发现以下代码看起来更简洁。

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


评论


这不是最好的方法,使用entrySet()效率更高。 Findbugs将标记此代码(请参阅findbugs.sourceforge.net/…)

–杰夫·奥尔森(Jeff Olson)
09年11月6日在20:46

@JeffOlson嗯,不是真的。映射查找为O(1),因此两个循环的行为均相同。诚然,在微型基准测试中它会稍微慢一些,但是我有时也会这样做,因为我讨厌一遍又一遍地写类型参数。同样,这很可能永远不会成为您的性能瓶颈,因此,如果它使代码更具可读性,那就去做吧。

–kritzikratzi
2012年10月8日13:25

更详细地讲:O(1)= 2 * O(1)几乎是大O表示法的定义。您说得对,因为它的运行速度稍慢,但就复杂性而言,它们是相同的。

–kritzikratzi
2012年10月8日23:26

通过碰撞与否,我的意思是几次碰撞都没关系,如果只有碰撞,那显然是另外一回事了。所以你很琐碎,但是,是的,你说的是真的。

–kritzikratzi
2012年12月23日在16:17

@杰夫·奥尔森(Jeff Olson):在只有一个恒定因素的情况下,“大O”复杂性不变的评论是正确的。不过,对我而言,手术需要一个小时还是两个小时仍然很重要。更重要的是,必须强调该因数不是2,因为对entrySet()进行迭代根本不承担任何查找;它只是所有条目的线性遍历。相反,在keySet()上进行迭代并对每个键执行一次查找将对每个键进行一次查找,因此,这里所说的是零查找与n次查找,n是Map的大小。因此,因素远超过2…

–霍尔格
16-11-9在12:22



#10 楼

仅供参考,如果您只对地图的键/值而不是其他感兴趣,还可以使用map.keySet()map.values()

#11 楼

使用Java 8,您可以使用forEach和lambda表达式来迭代Map,

map.forEach((k, v) -> System.out.println((k + ":" + v)));


#12 楼

对于Eclipse Collections,您将在forEachKeyValue接口上使用MapIterable方法,该方法由MutableMapImmutableMap接口及其实现继承。

MutableBag<String> result = Bags.mutable.empty();
MutableMap<Integer, String> map = Maps.mutable.of(1, "One", 2, "Two", 3, "Three");
map.forEachKeyValue((key, value) -> result.add(key + value));
Assert.assertEquals(Bags.mutable.of("1One", "2Two", "3Three"), result);


使用匿名内部类,您可以编写如下代码:

final MutableBag<String> result = Bags.mutable.empty();
MutableMap<Integer, String> map = Maps.mutable.of(1, "One", 2, "Two", 3, "Three");
map.forEachKeyValue(new Procedure2<Integer, String>()
{
    public void value(Integer key, String value)
    {
        result.add(key + value);
    }
});
Assert.assertEquals(Bags.mutable.of("1One", "2Two", "3Three"), result);


注意:我是Eclipse Collections的提交者。

#13 楼

从理论上讲,最有效的方法将取决于Map的实现方式。官方的方法是调用map.entrySet(),它返回一组Map.Entry,每个entry.getKey()都包含一个键和一个值(entry.getValue()map.keySet())。使用map.entrySet()valueSet()还是其他方法的区别。但我想不出有人会这样写的原因。

是的,顺序将取决于实现-以及(可能)插入的顺序和其他难以控制的因素。

[edit]我最初写的是entrySet(),但是当然,实际上是答案。

#14 楼


Lambda Expression Java 8


在Java 1.8(Java 8)中,通过使用Aggregate操作(Stream操作)中的forEach方法看起来与Iterable的迭代器类似的方法,变得更加容易接口。

只需将以下语句复制粘贴到您的代码中,然后将HashMap变量从hm重命名为HashMap变量即可打印出键值对。

HashMap<Integer,Integer> hm = new HashMap<Integer, Integer>();
/*
 *     Logic to put the Key,Value pair in your HashMap hm
 */

// Print the key value pair in one line.

hm.forEach((k, v) -> System.out.println("key: " + k + " value:" + v));

// Just copy and paste above line to your code.


下面是我使用Lambda Expression尝试的示例代码。这个东西太酷了。必须尝试。

HashMap<Integer, Integer> hm = new HashMap<Integer, Integer>();
    Random rand = new Random(47);
    int i = 0;
    while(i < 5) {
        i++;
        int key = rand.nextInt(20);
        int value = rand.nextInt(50);
        System.out.println("Inserting key: " + key + " Value: " + value);
        Integer imap = hm.put(key, value);
        if( imap == null) {
            System.out.println("Inserted");
        } else {
            System.out.println("Replaced with " + imap);
        }               
    }

    hm.forEach((k, v) -> System.out.println("key: " + k + " value:" + v));

Output:

Inserting key: 18 Value: 5
Inserted
Inserting key: 13 Value: 11
Inserted
Inserting key: 1 Value: 29
Inserted
Inserting key: 8 Value: 0
Inserted
Inserting key: 2 Value: 7
Inserted
key: 1 value:29
key: 18 value:5
key: 2 value:7
key: 8 value:0
key: 13 value:11


也可以使用Spliterator来实现相同的功能。

Spliterator sit = hm.entrySet().spliterator();


UPDATE


包括指向Oracle Docs的文档链接。
有关Lambda的更多信息,请访问此链接,必须阅读Aggregate Operations;对于Spliterator,请访问此链接。

#15 楼

Java 8

我们已经有了forEach方法,该方法接受一个lambda表达式。我们也有流API。考虑一个映射:

Map<String,String> sample = new HashMap<>();
sample.put("A","Apple");
sample.put("B", "Ball");


迭代键:

sample.keySet().forEach((k) -> System.out.println(k));


迭代值:

sample.values().forEach((v) -> System.out.println(v));


遍历条目(使用每个流):

sample.forEach((k,v) -> System.out.println(k + ":" + v)); 
sample.entrySet().stream().forEach((entry) -> {
            Object currentKey = entry.getKey();
            Object currentValue = entry.getValue();
            System.out.println(currentKey + ":" + currentValue);
        });


流的优点是可以轻松并行化它们万一我们想要。我们只需要使用parallelStream()代替上面的stream()

forEachOrderedforEach带流吗?
forEach不遵循遇到顺序(如果已定义),并且本质上是不确定的,其中就像forEachOrdered一样。因此,forEach不保证会保留订单。另请检查此内容。

#16 楼

Java 8:

您可以使用lambda表达式:

myMap.entrySet().stream().forEach((entry) -> {
    Object currentKey = entry.getKey();
    Object currentValue = entry.getValue();
});


有关更多信息,请遵循此。

评论


@injecteer:似乎是lambda表达式的动机

– humblerookie
2014年8月21日在19:49

如果您只想遍历地图,则不需要流。 myMap.forEach((currentKey,currentValue)-> / * action * /);更简洁。

–霍尔格
16年11月9日,12:37



#17 楼

使用Java 1.4进行尝试:

for( Iterator entries = myMap.entrySet().iterator(); entries.hasNext();){

  Entry entry = (Entry) entries.next();

  System.out.println(entry.getKey() + "/" + entry.getValue());

  //...
}


#18 楼

在地图中,能否对keys和/或values和/或both (e.g., entrySet)进行迭代取决于您的兴趣_像:


迭代地图的keys -> keySet()
 Map<String, Object> map = ...;

 for (String key : map.keySet()) {
     //your Business logic...
 }



遍历地图的values -> values()
 for (Object value : map.values()) {
     //your Business logic...
 }



遍历地图的both -> entrySet()
 for (Map.Entry<String, Object> entry : map.entrySet()) {
     String key = entry.getKey();
     Object value = entry.getValue();
     //your Business logic...
 }



此外,有3种不同的方法可以遍历HashMap。它们如下:
//1.
for (Map.Entry entry : hm.entrySet()) {
    System.out.print("key,val: ");
    System.out.println(entry.getKey() + "," + entry.getValue());
}

//2.
Iterator iter = hm.keySet().iterator();
while(iter.hasNext()) {
    Integer key = (Integer)iter.next();
    String val = (String)hm.get(key);
    System.out.println("key,val: " + key + "," + val);
}

//3.
Iterator it = hm.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry entry = (Map.Entry) it.next();
    Integer key = (Integer)entry.getKey();
    String val = (String)entry.getValue();
    System.out.println("key,val: " + key + "," + val);
}


#19 楼

使用Java 8最紧凑:

map.entrySet().forEach(System.out::println);


#20 楼

如果您有通用的无类型Map,则可以使用:

Map map = new HashMap();
for (Map.Entry entry : ((Set<Map.Entry>) map.entrySet())) {
    System.out.println(entry.getKey() + "/" + entry.getValue());
}


#21 楼

public class abcd{
    public static void main(String[] args)
    {
       Map<Integer, String> testMap = new HashMap<Integer, String>();
        testMap.put(10, "a");
        testMap.put(20, "b");
        testMap.put(30, "c");
        testMap.put(40, "d");
        for (Integer key:testMap.keySet()) {
            String value=testMap.get(key);
            System.out.println(value);
        }
    }
}


OR

public class abcd {
    public static void main(String[] args)
    {
       Map<Integer, String> testMap = new HashMap<Integer, String>();
        testMap.put(10, "a");
        testMap.put(20, "b");
        testMap.put(30, "c");
        testMap.put(40, "d");
        for (Entry<Integer, String> entry : testMap.entrySet()) {
            Integer key=entry.getKey();
            String value=entry.getValue();
        }
    }
}


#22 楼


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


如果对您的应用程序而言,优先考虑循环键的效率,则请选择一个Map实现,以将键保持在所需的顺序。我对接口有什么实现?


是的,绝对可以。


有些Map实现承诺一定的迭代顺序,而其他的则没有。
Map的不同实现维持键值对的不同顺序。

我创建了该表,总结了与Java 11捆绑在一起的各种Map实现。特别是,请注意迭代顺序列。单击/轻按以缩放。



您可以看到有四个Map实现保持顺序:


TreeMap

ConcurrentSkipListMap
LinkedHashMap
EnumMap


NavigableMap接口

其中两个实现NavigableMap接口:TreeMapConcurrentSkipListMap

较新的SortedMap接口有效地代替了较旧的NavigableMap接口。但是您可能会发现第三方实现仅实现了较旧的接口。

自然顺序

如果您想要一个Map来保持其对按“自然顺序”排列键,请使用TreeMapConcurrentSkipListMap。术语“自然顺序”是指按键的类别实现Comparable。通过compareTo方法返回的值用于排序比较。

自定义订单

如果要为密钥指定自定义排序例程以用于维持排序顺序,请传递适合于密钥类的Comparator实现。通过TreeMap使用ConcurrentSkipListMapComparator

原始插入顺序

如果您希望将地图对以原始顺序插入地图时保持原始顺序,请使用LinkedHashMap

枚举定义顺序

如果将DayOfWeekMonth之类的枚举用作键,请使用EnumMap类。此类不仅经过高度优化,以使用很少的内存并运行得非常快,而且还按枚举定义的顺序维护您的配对。例如,对于DayOfWeek,迭代时将首先找到DayOfWeek.MONDAY的密钥,而DayOfWeek.SUNDAY的密钥将是最后一个。

其他注意事项

选择Map实现时,还应考虑:


NULL。一些实现禁止/接受NULL作为键和/或值。
并发。如果要跨线程操作映射,则必须使用支持并发的实现。或使用Collections::synchronizedMap包装地图(不太理想)。

以上图表均涵盖了这两项注意事项。

评论


对该党的回答也很晚才发表评论(但很有帮助)。因为我是第一次听说EnumMap,所以我为此+1。在许多情况下,这可能会派上用场。

–user991710
2月16日18:42

#23 楼

排序将始终取决于特定的地图实现。
使用Java 8,您可以使用以下任意一种:
map.forEach((k,v) -> { System.out.println(k + ":" + v); });

或:
map.entrySet().forEach((e) -> {
            System.out.println(e.getKey() + " : " + e.getValue());
        });

结果将是相同(相同的顺序)。 entrySet由地图支持,因此您将获得相同的订单。第二个是方便的,因为它允许您使用lambda,例如如果您只想打印大于5的Integer对象:
map.entrySet()
    .stream()
    .filter(e-> e.getValue() > 5)
    .forEach(System.out::println);

下面的代码显示了通过LinkedHashMap和普通HashMap进行的迭代(示例)。您会看到顺序不同:
public class HMIteration {


    public static void main(String[] args) {
        Map<Object, Object> linkedHashMap = new LinkedHashMap<>();
        Map<Object, Object> hashMap = new HashMap<>();

        for (int i=10; i>=0; i--) {
            linkedHashMap.put(i, i);
            hashMap.put(i, i);
        }

        System.out.println("LinkedHashMap (1): ");
        linkedHashMap.forEach((k,v) -> { System.out.print(k + " (#="+k.hashCode() + "):" + v + ", "); });

        System.out.println("\nLinkedHashMap (2): ");

        linkedHashMap.entrySet().forEach((e) -> {
            System.out.print(e.getKey() + " : " + e.getValue() + ", ");
        });


        System.out.println("\n\nHashMap (1): ");
        hashMap.forEach((k,v) -> { System.out.print(k + " (#:"+k.hashCode() + "):" + v + ", "); });

        System.out.println("\nHashMap (2): ");

        hashMap.entrySet().forEach((e) -> {
            System.out.print(e.getKey() + " : " + e.getValue() + ", ");
        });
    }
}

输出:
LinkedHashMap (1):
10 (#=10):10, 9 (#=9):9, 8 (#=8):8, 7 (#=7):7, 6 (#=6):6, 5 (#=5):5, 4 (#=4):4, 3 (#=3):3, 2 (#=2):2, 1 (#=1):1, 0 (#=0):0,
LinkedHashMap (2):
10 : 10, 9 : 9, 8 : 8, 7 : 7, 6 : 6, 5 : 5, 4 : 4, 3 : 3, 2 : 2, 1 : 1, 0 : 0,
HashMap (1):
0 (#:0):0, 1 (#:1):1, 2 (#:2):2, 3 (#:3):3, 4 (#:4):4, 5 (#:5):5, 6 (#:6):6, 7 (#:7):7, 8 (#:8):8, 9 (#:9):9, 10 (#:10):10,
HashMap (2):
0 : 0, 1 : 1, 2 : 2, 3 : 3, 4 : 4, 5 : 5, 6 : 6, 7 : 7, 8 : 8, 9 : 9, 10 : 10,


#24 楼

    Iterator iterator = map.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry element = (Map.Entry)it.next();
        LOGGER.debug("Key: " + element.getKey());
        LOGGER.debug("value: " + element.getValue());    
    }


#25 楼

您可以使用泛型来实现:

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}


#26 楼

使用Java 8:

map.entrySet().forEach(entry -> System.out.println(entry.getValue()));


#27 楼

一个有效的Map迭代解决方案是从Java 5到Java 7的for循环。在这里,它是:
for (String key : phnMap.keySet()) {
    System.out.println("Key: " + key + " Value: " + phnMap.get(key));
}

从Java 8可以使用lambda表达式在Map上进行迭代。它是增强的forEach
phnMap.forEach((k,v) -> System.out.println("Key: " + k + " Value: " + v));

如果要编写lambda的条件,则可以这样编写:
phnMap.forEach((k,v)->{
    System.out.println("Key: " + k + " Value: " + v);
    if("abc".equals(k)){
        System.out.println("Hello abc");
    }
});


#28 楼

           //Functional Oprations
            Map<String, String> mapString = new HashMap<>();
            mapString.entrySet().stream().map((entry) -> {
                String mapKey = entry.getKey();
                return entry;
            }).forEach((entry) -> {
                String mapValue = entry.getValue();
            });

            //Intrator
            Map<String, String> mapString = new HashMap<>();
            for (Iterator<Map.Entry<String, String>> it = mapString.entrySet().iterator(); it.hasNext();) {
                Map.Entry<String, String> entry = it.next();
                String mapKey = entry.getKey();
                String mapValue = entry.getValue();
            }

            //Simple for loop
            Map<String, String> mapString = new HashMap<>();
            for (Map.Entry<String, String> entry : mapString.entrySet()) {
                String mapKey = entry.getKey();
                String mapValue = entry.getValue();

            }


#29 楼

是的,因为许多人都认为这是对Map进行迭代的最佳方法。

但是如果地图为nullpointerexception,就有机会抛出null。不要忘记将null放入。

                                                 |
                                                 |
                                         - - - -
                                       |
                                       |
for (Map.Entry<String, Object> entry : map.entrySet()) {
    String key = entry.getKey();
    Object value = entry.getValue();
}


#30 楼

有很多方法可以做到这一点。下面是一些简单的步骤:

假设您有一个地图,例如:

Map<String, Integer> m = new HashMap<String, Integer>();


然后您可以执行以下操作来迭代地图元素。

// ********** Using an iterator ****************
Iterator<Entry<String, Integer>> me = m.entrySet().iterator();
while(me.hasNext()){
    Entry<String, Integer> pair = me.next();
    System.out.println(pair.getKey() + ":" + pair.getValue());
}

// *********** Using foreach ************************
for(Entry<String, Integer> me : m.entrySet()){
    System.out.println(me.getKey() + " : " + me.getValue());
}

// *********** Using keySet *****************************
for(String s : m.keySet()){
    System.out.println(s + " : " + m.get(s));
}

// *********** Using keySet and iterator *****************
Iterator<String> me = m.keySet().iterator();
while(me.hasNext()){
    String key = me.next();
    System.out.println(key + " : " + m.get(key));
}