For-each 和 Index-for 循环最佳实践

For-each 和 Index-for 循环最佳实践

for-each 循环优先于传统的for循环 -- Joshua Blochhtml

a hand-written counted loop is better than for the enhanced loop. -- jackiemliujava

本文仅限于 ArrayList,LinkedList 不在讨论话题内。android

首先,相信你们对于这两种循环都很熟悉:缓存

// foreach
for (Box box : boxList)
    box.id++;

// index for(记住保存len,不然每次都须要调用boxList.size())
for (int index = 0,len = boxList.size(); index < len; index++)
    boxList.get(index).id++;复制代码

但二者什么时候使用,性能和效率怎么权衡,又成为了新的问题!性能优化

所以这篇文章就主要是针对这两种循环方式和 Android 平台上的取舍作一些简单的分析。架构

最佳实践

先给出最佳实践,缘由后面进行分析:并发

一、优先使用 index-for 模式(Android Framework 推荐);oop

二、若是想在遍历过程当中暴露出其它线程正在修改(ConcurrentModificationException)的问题,请使用 for-each 模式.性能

深刻分析

Iterate 模式

for each这种写法实际上是一个语法糖,其实优化

for(Box box:boxList) 等同于 for(Iterator var1 = boxList.iterator(); var1.hasNext();var1.next())

能够看到这种模式会去额外生成一个 Iterator 对象,因此相较于 Index 模式而言,它会额外使用一些内存。

在 Android 平台,内存资源是极为有限的,若是只是单层的循环还算 OK,可是若是是多层循环,

或者是隐式的多层循环中使用 Iterate 模式,可想而知内存会临时分配不少个变量。

例如:

// 显式多层循环
for (Box box : boxList)
  for (InnerBox innerBox : box)
    ; // do something

// 隐式的多层循环(View 的 onDraw 方法)
void onDraw(Canvas c){
  for (Box box : boxList)
    ; // do something
}复制代码

以上这两种状况可能来讲就会分配过多的临时对象,致使内存不足进行 GC ,从而影响 App 流畅度。

除此以外,在迭代的过程当中,会去调用 Iterator 的 next(),这里我以 ArrayList 为例:

public E next() {
    checkForComodification();  // 检查是否在遍历过程当中有人修改了列表
     int i = cursor;
     if (i >= size)    // 检查下标是否合法
         throw new NoSuchElementException();
     Object[] elementData = ArrayList.this.elementData;
     if (i >= elementData.length) // 检查下标是否合法
         throw new ConcurrentModificationException();
     cursor = i + 1;
     return (E) elementData[lastRet = i];  // 返回对象
}复制代码

相比与Index 模式,它增长了不少检查,因此会带来必定的开销,但也是一种特性(检查遍历中是否有人修改)。

Index 模式

你们最多见的多是:

for(int index = 0; index < boxList.size();index++)
    ; // do something复制代码

但这种方式其实并不够理想,由于每次循环时,都会去调用 List.size()。

因此咱们能够将其保存下来:

for(int index = 0,len = boxList.size(); index < len;index++)
    ; // do something复制代码

相对而言,保存 len 的方法更快,尤为在 Effective Java 中的范例:

for(int index = 0,n = expensiveComputation(); index < n;index++)
    ; // do something复制代码

若是一个方法耗时较多且结果不会改变,那么能够用一个临时变量充当缓存。

性能比较

速度

一开始我还本身在电脑上作了个小 demo,然而发现 Android Team 已经作了一个更具表明性的(屡次取平均值),这里直接借用吧:

mark
mark

这里的案例是 400,000 随机的 Integer,每种模式都跑 10 次,去掉最大最小值后取平均结果。

能够很明显看出 Index 模式的耗时完美胜出,Yeah |ω・)

内存占用

因为以前介绍了 Iterate 模式会初始化一个 Iterator 对象,因此它的内存占用确定多于 Index 模式。

总结

0、语法糖可能很甜,但也可能有隐藏的性能损耗(e.g lambda 表达式会增长运行时开销)

一、从语法上而言,foreach 这种更简洁,也更加地隐藏了细节(语法糖),但也缺失了某些特性。

简单来讲,尽量将 foreach 当作是一种 只读向后遍历

在 Effective Java 中,介绍了没法使用的三种场景:

  • 过滤——若是须要在遍历过程当中删除元素。若是在 foreach 中删除,会引起并发修改的异常。
  • 转换——若是须要在遍历过程当中对元素进行替换。若是在 foreach 中替换,例如 o = new Object(); 其实并无改变列表中的值。
  • 平行迭代——若是须要灵活更改遍历的顺序时。

二、开发 Android 的时候,尽量放弃使用 foreach ,减小内存压力。

三、若是嫌弃 Index 模式的模板代码太麻烦,能够试试 Live Templates 中自带的 itli ,一键生成循环代码哦~

四、在写 Index 模式的时候,尽量去保存 list.size(),虽然 JIT 有可能会进行优化,但这种方式能够更加保险。

五、性能优化不仅是总体架构或者类库的优化,也要从平时点滴作起。

最后,推荐你们看看: Performance Tips

参考:

To Index or Iterate?

Use Enhanced For Loop Syntax

相关文章
相关标签/搜索