牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了( 二 )


源码:
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
编译后的字节码:
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
都不用耐心观察 , 两个方法的字节码除了局部变量不一样其他都几乎一样 , 由此可以得出增强型for循环性能与迭代器一样 , 实际运行结果也一样
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
Map.forEach
先说一下为什么不把各种方法一起运行同时打印性能 , 这是因为CPU缓存的原因和JVM的一些优化会干扰到性能的判断 , 附录全部测试结果有说明
直接来看源码吧
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
很简短的源码 , 就不打注释了 , 从源码我们不难获取到以下信息:
该方法也是快速失败的 , 遍历期间不能删除元素需要遍历整个数组BiConsumer加了@FunctionalInterface注解 , 用了 lambda第三点和性能无关 , 这里只是提下
通过以上信息我们能确定这个性能与table数组的大小有关 。
但是在实际测试的时候却发现性能比迭代器差了不少:
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
其中详细原因等我下期的文章吧 , 这里不讲了
Stream.forEach
Stream与Map.forEach的共同点是都使用了lambda表达式 。 但两者的源码没有任何复用的地方 。
不知道你有没有看累 , 先上测试结果吧:
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
耗时比Map.foreach还要高点 。
下面讲讲Straam.foreach顺序流的源码 , 这个也不复杂 , 不过累的话先去看看总结吧 。
Stream.foreach的执行者是分流器 , HashMap的分流器源码就在HashMap类中 , 是一个静态内部类 , 类名叫 EntrySpliterator
下面是顺序流执行的方法
牛皮了!头一次见大佬把「Map遍历性能问题」详解得如此清晰明了
本文插图
从以上源码中我们也可以轻易得出遍历需要顺序扫描所有数组
总结
至此 , Map的四种遍历方法都测试完了 , 我们可以简单得出两个结论
Map的遍历性能与内部table数组大小有关 , 也就是说与常用参数 initial capacity 有关 , 不管哪种遍历方式都是的性能(由高到低):迭代器 == 增强型For循环 > Map.forEach > Stream.foreach这里就不说什么多少倍多少倍的性能差距了 , 抛开数据集大小都是扯淡 , 当我们不指定initial capacity的时候 , 四种遍历方法耗时都是3ms , 这3ms还是输入输出流的耗时 , 实际遍历耗时都是0 , 所以数据集不大的时候用哪种都无所谓 , 就像不加输入输出流耗时不到1ms一样 , 很多时候性能消耗是在遍历中的业务操作 , 这篇文章不是为了让你去优化代码把foreach改成迭代器的 , 在大多数场景下并不需要关注迭代本身的性能 , Stream与Lambda带来的可读性提升更加重要 。