Map 遍历分析
1. 阐述
对于Java中Map的遍历方式,很多文章都推荐使用entrySet,认为其比keySet的效率高很多。理由是:entrySet方法一次拿到所有key和value的集合;而keySet拿到的只是key的集合,针对每个key,都要去Map中额外查找一次value,从而降低了总体效率。那么实际情况如何呢?
为了解遍历性能的真实差距,包括在遍历key+value、遍历key、遍历value等不同场景下的差异,我试着进行了一些对比测试。
2. 对比测试
一开始只进行了简单的测试,但结果却表明keySet的性能更好,这一点让我很是费解,不都说entrySet明显好于keySet吗?为了进一步地进行验证,于是采用了不同的测试数据进行更详细的对比测试。
2.1 测试数据
2.1.1 HashMap测试数据
- HashMap-1,大小为100万,key和value均为String,key的值为1、2、3……1000000:
|
Map<String, String> map = new HashMap<String, String>(); String key, value; for (i = 1; i <= num; i++) { key = "" + i; value = "value"; map.put(key, value); } |
- HashMap-2,大小为100万,key和value均为String,key的值为50、100、150、200、……、50000000:
|
Map<String, String> map = new HashMap<String, String>(); String key, value; for (i = 1; i <= num; i++) { key = "" + (i * 50); value = "value"; map.put(key, value); } |
2.1.2 TreeMap测试数据
- TreeMap-1,大小为100万,key和value均为String,key的值为1、2、3……1000000:
|
Map<String, String> map = new TreeMap<String, String>(); String key, value; for (i = 1; i <= num; i++) { key = "" + i; value = "value"; map.put(key, value); } |
- TreeMap-2,大小为100万,key和value均为String,key的值为50、100、150、200、……、50000000,更离散:
|
Map<String, String> map = new TreeMap<String, String>(); String key, value; for (i = 1; i <= num; i++) { key = "" + (i * 50); value = "value"; map.put(key, value); } |
2.2 测试场景
分别使用keySet、entrySet和values的多种写法测试三种场景:遍历key+value、遍历key、遍历value的场景。
2.2.1 遍历key+value
- keySet遍历key+value(写法1):
|
Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { key = iter.next(); value = map.get(key); } |
- keySet遍历key+value(写法2):
|
for (String key : map.keySet()) { value = map.get(key); } |
- entrySet遍历key+value(写法1):
|
Iterator<Entry<String, String>> iter = map.entrySet().iterator(); Entry<String, String> entry; while (iter.hasNext()) { entry = iter.next(); key = entry.getKey(); value = entry.getValue(); } |
- entrySet遍历key+value(写法2):
|
for (Entry<String, String> entry: map.entrySet()) { key = entry.getKey(); value = entry.getValue(); } |
2.2.2 遍历key
- keySet遍历key(写法1):
|
Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { key = iter.next(); } |
- keySet遍历key(写法2):
|
for (String key : map.keySet()) { } |
- entrySet遍历key(写法1):
|
Iterator<Entry<String, String>> iter = map.entrySet().iterator(); while (iter.hasNext()) { key = iter.next().getKey(); } |
- entrySet遍历key(写法2):
|
for (Entry<String, String> entry: map.entrySet()) { key = entry.getKey(); } |
2.2.3 遍历value
- keySet遍历value(写法1):
|
Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { value = map.get(iter.next()); } |
- keySet遍历value(写法2):
|
for (String key : map.keySet()) { value = map.get(key); } |
- entrySet遍历value(写法1):
|
Iterator<Entry<String, String>> iter = map.entrySet().iterator(); while (iter.hasNext()) { value = iter.next().getValue(); } |
- entrySet遍历value(写法2):
|
for (Entry<String, String> entry: map.entrySet()) { value = entry.getValue(); } |
- values遍历value(写法1):
|
Iterator<String> iter = map.values().iterator(); while (iter.hasNext()) { value = iter.next(); } |
- values遍历value(写法2):
|
for (String value : map.values()) { } |
2.3 测试结果
2.3.1 HashMap测试结果
|
单位:毫秒 |
HashMap-1 |
HashMap-2 |
|
keySet遍历key+value(写法1) |
39 |
93 |
|
keySet遍历key+value(写法2) |
38 |
87 |
|
entrySet遍历key+value(写法1) |
43 |
86 |
|
entrySet遍历key+value(写法2) |
43 |
85 |
|
单位:毫秒 |
HashMap-1 |
HashMap-2 |
|
keySet遍历key(写法1) |
27 |
65 |
|
keySet遍历key(写法2) |
26 |
64 |
|
entrySet遍历key(写法1) |
35 |
75 |
|
entrySet遍历key(写法2) |
34 |
74 |
|
单位:毫秒 |
HashMap-1 |
HashMap-2 |
|
keySet遍历value(写法1) |
38 |
87 |
|
keySet遍历value(写法2) |
37 |
87 |
|
entrySet遍历value(写法1) |
34 |
61 |
|
entrySet遍历value(写法2) |
32 |
62 |
|
values遍历value(写法1) |
26 |
48 |
|
values遍历value(写法2) |
26 |
48 |
2.3.2 TreeMap测试结果
|
单位:毫秒 |
TreeMap-1 |
TreeMap-2 |
|
keySet遍历key+value(写法1) |
430 |
451 |
|
keySet遍历key+value(写法2) |
429 |
450 |
|
entrySet遍历key+value(写法1) |
77 |
84 |
|
entrySet遍历key+value(写法2) |
70 |
68 |
|
单位:毫秒 |
TreeMap-1 |
TreeMap-2 |
|
keySet遍历key(写法1) |
50 |
49 |
|
keySet遍历key(写法2) |
49 |
48 |
|
entrySet遍历key(写法1) |
66 |
64 |
|
entrySet遍历key(写法2) |
65 |
63 |
|
单位:毫秒 |
TreeMap-1 |
TreeMap-2 |
|
keySet遍历value(写法1) |
432 |
448 |
|
keySet遍历value(写法2) |
430 |
448 |
|
entrySet遍历value(写法1) |
62 |
61 |
|
entrySet遍历value(写法2) |
62 |
61 |
|
values遍历value(写法1) |
46 |
46 |
|
values遍历value(写法2) |
45 |
46 |
3. 结论
3.1 如果你使用HashMap
- 同时遍历key和value时,keySet与entrySet方法的性能差异取决于key的具体情况,如复杂度(复杂对象)、离散度、冲突率等。换言之,取决于HashMap查找value的开销。entrySet一次性取出所有key和value的操作是有性能开销的,当这个损失小于HashMap查找value的开销时,entrySet的性能优势就会体现出来。例如上述对比测试中,当key是最简单的数值字符串时,keySet可能反而会更高效,耗时比entrySet少10%。总体来说还是推荐使用entrySet。因为当key很简单时,其性能或许会略低于keySet,但却是可控的;而随着key的复杂化,entrySet的优势将会明显体现出来。当然,我们可以根据实际情况进行选择
- 只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间。在上述测试结果中,keySet比entrySet方法耗时少23%。
- 只遍历value时,使用vlaues方法是最佳选择,entrySet会略好于keySet方法。
- 在不同的遍历写法中,推荐使用如下写法,其效率略高一些:
|
for (String key : map.keySet()) { value = map.get(key); } |
|
for (Entry<String, String> entry: map.entrySet()) { key = entry.getKey(); value = entry.getValue(); } |
|
for (String value : map.values()) { } |
3.2 如果你使用TreeMap
- 同时遍历key和value时,与HashMap不同,entrySet的性能远远高于keySet。这是由TreeMap的查询效率决定的,也就是说,TreeMap查找value的开销较大,明显高于entrySet一次性取出所有key和value的开销。因此,遍历TreeMap时强烈推荐使用entrySet方法。
- 只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间。在上述测试结果中,keySet比entrySet方法耗时少24%。
- 只遍历value时,使用vlaues方法是最佳选择,entrySet也明显优于keySet方法。
- 在不同的遍历写法中,推荐使用如下写法,其效率略高一些:
|
for (String key : map.keySet()) { value = map.get(key); } |
|
for (Entry<String, String> entry: map.entrySet()) { key = entry.getKey(); value = entry.getValue(); } |
|
for (String value : map.values()) { } |
Map 遍历分析的更多相关文章
- 分页查询和分页缓存查询,List<Map<String, Object>>遍历和Map遍历
分页查询 String sql = "返回所有符合条件记录的待分页SQL语句"; int start = (page - 1) * limit + 1; int end = pag ...
- js中三个对数组操作的函数 indexOf()方法 filter筛选 forEach遍历 map遍历
indexOf()方法 indexOf()方法返回在该数组中第一个找到的元素位置,如果它不存在则返回-1. 不使用indexOf时 var arr = ['apple','orange','pea ...
- KEIL MDK输出map文件分析
一.文件分析流程 1.第一部分:Section Cross References 主要是各个源文件生成的模块之间相互引用的关系. stm32f10x.o(STACK) refers (Special) ...
- map遍历的四种方式
原文 http://blog.csdn.net/dayanxuqun/article/details/26348277 以下是map遍历的四种方式: // 一.推荐只用value的时候用,都懂的... ...
- 原生JS forEach()和map()遍历的区别以及兼容写法
一.原生JS forEach()和map()遍历 共同点: 1.都是循环遍历数组中的每一项. 2.forEach() 和 map() 里面每一次执行匿名函数都支持3个参数:数组中的当前项item,当前 ...
- map遍历性能记录
map遍历可以通过keySet或者entrySet方式. 性能上:entrySet略胜一筹,原因是keySet获取到key后再根据key去获取value,在查一遍,所以慢一些. keySet: //先 ...
- forEach() 和 map() 遍历
1.forEach() 没有返回值. arr[].forEach(function(value,index,array){ //do something }) 参数:value数组中的当前项, i ...
- js的map遍历和array遍历
1. array遍历: [1].forEach() forEach是ES5中操作数组的一种方法,主要功能是遍历数组.forEach方法中的function回调有三个参数:第一个参数是遍历的数组内容,第 ...
- react.js map遍历的问题
React遍历多个Ant Design中的Upload组件时,随意删除任一个Upload出现了bug,依次点击上传图片后,当点击删除时,倒着删除没有问题,从中间和从开头删问题出现了,出现了类似塌方的效 ...
随机推荐
- 漫谈android系统(4)bring up panel
点击打开链接 版权声明: 作者:alex wang 版权:本文版权归作者和CSDN共有 转载:欢迎转载,为了保存作者的创作热情,请按要求[转载],谢谢 要求:未经作者同意,必须保留此段声明:必须在文章 ...
- 字符串的n位左旋
要求:将主串的某一段(n位)翻转到主串的最后,如:abcdef以2位翻转则结果为:cdefab.要求时间复杂度为O(n),空间复杂度为O(1) 思路一:可以重新定义一个与原串相同大小的字符数组resu ...
- UNIX环境高级编程——可靠信号与不可靠信号
在早期的UNIX中信号是不可靠的,不可靠在这里指的是:信号可能丢失,一个信号发生了,但进程却可能一直不知道这一点. 现在Linux 在SIGRTMIN实时信号之前的都叫不可靠信号,这里的不可靠主要是不 ...
- Cocos2D中使用CCDrawNode绘制几何图形崩溃的解决
在cocos2D v3.x中已经不能像在v2.x中那样直接调用ccDrawXXX函数来绘制几何图形了. 我们可以使用CCDrawNode或者CCRenderer来绘制图形. 但是官方的Api手册中说的 ...
- Android进阶(五)在Eclipse中关联Gson源码
1)获得相应的jar包gson-2.2.4-sources.jar与gson-2.2.4-javadoc.jar. 2)在libs目录下新建src包和docs包,将相应jar包放入相应文件夹内. 3) ...
- Android 官方命令深入分析之Android Debug Bridge(adb)
作者:宋志辉 Android Debug Brideg(adb)是一个多用途的命令行工具.可以与Android虚拟机进行通信或连接真机.它同样提供了访问设备shell的高级命令行操作的权限.它是一个包 ...
- ABB机器人基础培训资料整理与总结
之前对机械臂了解较少,这方面知识比较匮乏.只使用过PowercCube六自由度机械臂. 感谢ABB公司何老师的耐心指导. 学习资料汇总:(最重要的ABB Robot 官网就不列出了,这里以中文资料为主 ...
- B/S和C/S架构图解
软件:B/S和C/S两种架构模式.接下来用三张图片解释,什么是B/S什么是C/S. 图片一:软件架构模式 图片二:C/S结构模式 图片三:B/S结构模式 相信图解胜过冗长文字的解释,什么是B/S什么是 ...
- 学习tornado:介绍
前言 在python里面,有许多web framework.对于我来说,因为很长一段时间都在使用tornado,所以有了一些心得体会.虽然现在我的工作重点已经转向go了,但是我觉得还是有必要记录一下t ...
- 【算法导论】单源最短路径之Dijkstra算法
Dijkstra算法解决了有向图上带正权值的单源最短路径问题,其运行时间要比Bellman-Ford算法低,但适用范围比Bellman-Ford算法窄. 迪杰斯特拉提出的按路径长度递增次序来产生源点到 ...