Java集合 - map中的keySet()和entrySet()有什么区别?

38

我把一个字符串数组的元素放在一个映射表中,其中字符串数组的元素是键,单词频率是值,例如:

String[] args = {"if","it","is","to","be","it","is","up","me","to","delegate"};

那么这个映射将会包含类似于 [ if:1, it:2 .... ] 的条目。

Set<String> keys = m.keySet();
System.out.println("keyset of the map : "+keys);

打印所有键:"if","it","is","to","be","it","is","up","me","to","delegate"

Set<Map.Entry<String, Integer>> entrySet = m.entrySet();
Iterator<Map.Entry<String, Integer>> i = entrySet.iterator();
while(i.hasNext()){
    Map.Entry<String, Integer> element = i.next();
    System.out.println("Key: "+element.getKey()+" ,value: "+element.getValue());
}

打印所有键值对:

使用entry set打印所有值:

Key: if ,value: 1
Key: it ,value: 2
Key: is ,value: 2
Key: to ,value: 2
Key: be ,value: 1
Key: up ,value: 1
Key: me ,value: 1
Key: delegate ,value: 1

但是下面的代码块应该打印出与上述相同的输出,但实际上并没有:

Iterator<String> itr2 = keys.iterator();
while(itr2.hasNext()){
    //System.out.println(itr1.next()+" ");
    //System.out.println(m.get(itr1.next())+" ");
    System.out.println("Key: "+itr2.next()+" ,value: "+m.get(itr2.next()));
}

它会输出:

Key: if ,value: 2
Key: is ,value: 2
Key: be ,value: 1
Key: me ,value: 1

但是,如果我们取消while循环中的第1行注释,即:

System.out.println(itr1.next()+" ");

注释掉这行代码

System.out.println("Key: "+itr2.next()+" ,value: "+m.get(itr2.next()));

然后我们获取所有键:{"if","it","is","to","be","it","is","up","me","to","delegate"}

如果我们在使用m.get()时与itr2.next()一起使用,那么迭代器将没有几个键!


如果您正在存储许多整数值,您应该考虑使用fastutil库而不是j.u集合。 - bmargulies
键集不会重复,我猜在此调用后不应该再打印“it”字符串两次。以下是代码: Set<String> keys = m.keySet(); System.out.println("地图的键集 :"+keys); - John Doe
5个回答

56
每次调用Iterator.next()都会将迭代器移动到下一个元素。如果您希望在多个语句或表达式中使用当前元素,则必须将其存储在本地变量中。甚至更好的方法是,为什么不直接使用for-each循环呢?
for (String key : map.keySet()) {
    System.out.println(key + ":" + map.get(key));
}

此外,遍历entrySet更快,因为您不需要为每个键查询两次映射。此外,Map.Entry实现通常会实现toString()方法,因此您无需手动打印键值对。

for (Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry);
}

5
谢谢您提到这一非常重要的观点:使用entry set可以避免调用get()带来的不必要开销。 - erickson
因为你不需要为每个键查询两次映射表。为什么要查询两次?我认为当你执行map.get(key)时只需要查询一次。 - HenryNguyen
1
@HenryNguyen 我相信他的意思是当你使用迭代器获取键时,你只查询了一次。 - Evil Washing Machine

3

遍历大型地图时,entrySet()keySet()要好得多。查看教程,了解如何使用entrySet()优化大对象的遍历,并如何帮助性能调整。


3

每次调用itr2.next(),你都会获得一个不同的值。不是同一个值。在循环中只应该调用一次。

Iterator<String> itr2 = keys.iterator();
    while(itr2.hasNext()){
        String v = itr2.next();
        System.out.println("Key: "+v+" ,value: "+m.get(v));
    }

2
在《Effective Java》一书中提到了同样的错误,这就是为什么使用foreach循环是首选的原因。 - Amir Pashazadeh
你的映射表中有偶数个条目真是幸运,否则你将会遇到一个 RuntimeException。 - Amir Pashazadeh
对Amir的评论点赞。除非你需要从集合中删除项目,否则直接使用迭代器只会引入错误的可能性。对于简单的读取循环,应该使用foreach循环样式。 - Mike Yockey

1

迭代器 只能向前移动,如果已读取一次,则完成。您的

m.get(itr2.next());

读取itr2.next();的下一个值,这就是为什么您会丢失一些(实际上是每隔一个)键。


0
为了简化事情,请注意每次执行itr2.next()时,指针都会移动到下一个元素,即如果您仔细观察,则根据您编写的逻辑,输出是完全正确的。
这可能有助于您更好地理解:

While循环的第一次迭代(指针在第一个元素之前):
键:if,值:2 {itr2.next()=if; m.get(itr2.next()=it)=>2}

While循环的第二次迭代(指针在第三个元素之前):
键:is,值:2 {itr2.next()=is; m.get(itr2.next()=to)=>2}

While循环的第三次迭代(指针在第五个元素之前):
键:be,值:1 {itr2.next()="be"; m.get(itr2.next()="up")=>"1"}

While循环的第四次迭代(指针在第七个元素之前):
键:me,值:1 {itr2.next()="me"; m.get(itr2.next()="delegate")=>"1"}

键:if,值:1
键:it,值:2
键:is,值:2
键:to,值:2
键:be,值:1
键:up,值:1
键:me,值:1
键:delegate,值:1

输出结果为:

键:if,值:2
键:is,值:2
键:be,值:1
键:me,值:1


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接