背景是这样的,产品开发过程中,有个字体json文件需要截取的接口开发,因为涉及到多层的json内容遍历,于是就和循环过不去了。这篇文章主要讨论java中for循环,for-each(增强for循环),forEach以及Iterator迭代器的区别和联系。结论先行:
联系:
区别:
Iterator iterator = integerList.iterator(); while (iterator.hasNext()) { Integer integer = (Integer) iterator.next(); }
当我们需要遍历不同的集合时,我们只需要传递集合的iterator,如integerList.iterator(),这就 是Iterator 的好处,他不包含任何有关他所遍历的集合的类型信息,能够将遍历集合的操作与集合底层的数据结构分离。迭代器统一了对集合的访问方式。这也是接口解耦的最佳实践之一。
下面是我所用的for循环, for-each,forEach以及Iterator迭代器效率测试程序和效果截图:
public class ForIteratorForEachTest { public static void main(String[] args) { List<Integer> integerList = new ArrayList<Integer>(); Integer intValue = 0; AtomicReference<Integer> value = new AtomicReference<>(0); long size = 100000; // 初始化待测试集合 for (int i = 0; i < size; i++) { integerList.add(i); } // 开始时间 long startTime = System.nanoTime(); // for循环 /*for (int i = 0; i < size; i++) { intValue = integerList.get(i); }*/ // for-each 循环 /* for (Integer integer : integerList) { intValue = integer; }*/ // forEach 循环 /*integerList.forEach((integer) -> { value.set(integer); });*/ // iterator Iterator iterator = integerList.iterator(); while (iterator.hasNext()) { Integer integer = (Integer) iterator.next(); } // 结束时间 long endTime = System.nanoTime(); System.out.println("#========= 方法耗时: " + (endTime - startTime) + " 纳秒 =========#"); } }
for循环,for-each循环(增强for循环)和Iterator效率差不多,甚至在数据量小的情况下,for循环效率更高。在10w,100w,1000w三种级别forEach都是最慢的。通过翻JDK1.8的源码源码我发现了原因:
1.每个元素遍历前都进行了判空操作。
2.因为是流管道操作,所以他的元素数据结构需要保证原子性,这里又是一层性能消耗。
/** * Performs the given action for each element of the {@code Iterable} * until all elements have been processed or the action throws an * exception. Unless otherwise specified by the implementing class, * actions are performed in the order of iteration (if an iteration order * is specified). Exceptions thrown by the action are relayed to the * caller. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code * for (T t : this) * action.accept(t); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */ default void forEach(Consumer<? super T> action) { // 先判空 Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
Java神书之一 《Effective Java》 中第九章,58节提到,Prefer for-each loops to traditional for loops(for-each 循环优于传统的 for 循环),他给出的原因是:
”迭代器和索引变量都很混乱(你只需要元素)。此外,它们有出错的可能。迭代器在每个循环中出现三次,索引变量出现四次,这使得有很多机会使用到错误的变量。如果这样做,就不能保证编译器会捕捉到问题。最后,这两个循环区别很大,for循环还需要额外注意容器类型,并给类型转换增加小麻烦。“for-each 循环(官方称为「enhanced for 语句」)解决了所有这些问题。它通过隐藏迭代器或索引变量来消除混乱和出错的机会。文中也指出了三个for-each循环不适应的场景:
如果需要遍历一个集合并删除选定元素,则需要使用显式的迭代器,以便调用其 remove 方法。通过使用 Collection 在 Java 8 中添加的 removeIf 方法,通常可以避免显式遍历。
如果需要遍历一个 List 或数组并替换其中部分或全部元素的值,那么需要 List 迭代器或数组索引来替换元素的值。
如果需要并行遍历多个集合,那么需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步执行(如上述牌和骰子示例中无意中演示的错误那样)。如果发现自己处于这些情况中的任何一种,请使用普通的 for 循环,并警惕本条目中提到的陷阱。
参考资料:
https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual