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

相關文章
相關標籤/搜索