相信大家在工作中使用集合已經算是家常便飯了吧,而對集合進行遍歷也算是必不可少的操作了。而對集合進行遍歷也有多種方法,而常用的一般就是for循環和增強for循環(也叫foreach循環)。可能有些人有些迷惑,這兩種循環有什麼區別呢?我們該如何選擇使用呢?用好了系統的性能也能得到一點點的優化哦,接下來就解開神祕的面紗。
先來看一個需求:統計一個省的各科高考平均值,比如數學平均分數。
public static void main(String[] args) {
//學生數量 80萬
int stuNum = 80*10000;
//List集合,記錄所有學生的分數
List<Integer> scores = new ArrayList<Integer>(stuNum);
//寫入分數
for (int i = 0; i < stuNum; i++) {
scores.add(new Random().nextInt(150));
}
//記錄開始計算時間
long start = System.currentTimeMillis();
System.out.println("平均分是:"+average(scores));
System.out.println("執行時間:"+(System.currentTimeMillis()-start)+"ms");
}
private static int average(List<Integer> list) {
int sum = 0;
//foreach遍歷求和
for (int i : list) {
sum +=i;
}
return sum/list.size();
}
平均分:74
執行時間:16ms
如果我們換成第二種:
private static int average(List<Integer> list) {
int sum = 0;
//for循環遍歷求和
for (int i = 0; i < list.size(); i++) {
sum += list.get(i);
}
return sum/list.size();
}
平均分:74
執行時間:0ms
由於第二種執行速度較快在毫秒以內所以顯示0ms,可以看到性能提升了不少,當數據量更大時效果會更明顯。那爲什麼第二種遍歷會有這麼高的性能提升呢?
這是因爲ArrayList數組實現了RandomAccess接口(隨機存取接口),這也就標誌着ArrayList是一個可以隨機存取的列表。在java中,RandomAccess和Cloneable、Serializable一樣,都是標識性接口,不需要任何實現,只是用來表明其實現類具有某種特質的,實現了Cloneable表名可以被拷貝,實現了Serializable接口表明被序列化了,實現了RandomAccess接口表明這個類可以隨機存取,對我們的ArrayList來說也就標誌着其數據元素之間沒有關聯,即兩個位置相鄰的元素之間沒有互相依賴和索引關係,可以隨機訪問和存儲。所以我們說ArrayList是無序的集合。
我們知道,java中的foreach語法是iterator(迭代器)的變形用法,也就是說上面的foreach與下面的代碼等價:
for(Iterator<Integer> i = list.iterator(); i.hasNext();){
sum += i.next();
}
迭代器是23個設計模式中的一種,”提供一種方法訪問一個容器對象中的各個元素,同時又無須暴露該對象的內部細節”,也就是說對於ArrayList,需要先創建一個迭代器容器,然後屏蔽內部遍歷細節,對外提供hasNext、nest等方法。而ArrayList實現了RandomAccess接口,已表明元素之間本來沒有關係,可是,爲了使用迭代器就需要強制建立一種互相“知曉”的關係,比如上一個元素可以判斷是否有下一個元素,以及下一個元素是什麼等關係,這也就是通過foreach遍歷耗時的原因。
java爲ArrayList類加上了RandomAccess接口,就是在告訴我們,“嘿,ArrayList是隨機存取的”採用下標方式遍歷列表速度會更快”,那爲什麼不把RandomAccess加到所有的List實現類上呢?
那是因爲有些List實現類不是隨機存取的,而是有序存取的,比如LinkedList類,LinkedList也是一個列表,但它實現了雙向鏈表,每個數據節點中都有三個數據項:前節點的應用(Previous Node)、本節點元素(Node Element)、後繼節點的引用(Next Node),也就是說在LinkedList中的兩個元素本來就是有關聯,所以對於LinkedList來說,使用foreach遍歷是不是效率更高呢?
public static void main(String[] args) {
//學生數量 80萬
int stuNum = 80*10000;
//List集合,記錄所有學生的分數
List<Integer> scores = new LinkedList<Integer>();
//寫入分數
for (int i = 0; i < stuNum; i++) {
scores.add(new Random().nextInt(150));
}
//記錄開始計算時間
long start = System.currentTimeMillis();
System.out.println("平均分是:"+average(scores));
System.out.println("執行時間:"+(System.currentTimeMillis()-start)+"ms");
}
private static int average(List<Integer> list) {
int sum = 0;
//foreach遍歷求和
for (int i : list) {
sum +=i;
}
return sum/list.size();
}
運行結果也是0ms,效率也非常高。我們可以將average方法重構下,以便實現不同的列表採用不同的遍歷方式,代碼如下:
private static int average(List<Integer> list) {
int sum = 0;
if(list instanceof RandomAccess){
for (int i = 0; i < list.size(); i++) {
sum += list.get(i);
}
} else {
for (int i : list) {
sum +=i;
}
}
return sum/list.size();
}