主要介紹arraylist和linkedlist這兩種list的五種循環周遊方式,各種方式的性能測試對比,根據arraylist和linkedlist的源碼實作分析性能結果,總結結論。
通過本文你可以了解(1)list的五種周遊方式及各自性能 (2)foreach及iterator的實作 (3)加深對arraylist和linkedlist實作的了解。
閱讀本文前希望你已經了解arraylist順序存儲和linkedlist鍊式的結構,本文不對此進行介紹。
1. list的五種周遊方式
下面隻是簡單介紹各種周遊示例(以arraylist為例),各自優劣會在本文後面進行分析給出結論。
(1) for each循環
java
1
2
3
4
list<integer> list = new arraylist<integer>();
for (integer j : list) {
// use j
}
(2) 顯示調用集合疊代器
for (iterator<integer> iterator = list.iterator(); iterator.hasnext();) {
iterator.next();
或
5
iterator<integer> iterator = list.iterator();
while (iterator.hasnext()) {
(3) 下标遞增循環,終止條件為每次調用size()函數比較判斷
for (int j = 0; j < list.size(); j++) {
list.get(j);
(4) 下标遞增循環,終止條件為和等于size()的臨時變量比較判斷
int size = list.size();
for (int j = 0; j < size; j++) {
(5) 下标遞減循環
for (int j = list.size() - 1; j >= 0; j--) {
在測試前大家可以根據對arraylist和linkedlist資料結構及iterator的了解,想想上面五種周遊方式哪個性能更優。
2、list五種周遊方式的性能測試及對比
以下是性能測試代碼,會輸出不同數量級大小的arraylist和linkedlist各種周遊方式所花費的時間。
arraylist和linkedlist循環性能對比測試代碼
ps:如果運作報異常in thread “main” java.lang.outofmemoryerror: java heap space,請将main函數裡面list size的大小減小。
其中getarraylists函數會傳回不同size的arraylist,getlinkedlists函數會傳回不同size的linkedlist。
looplistcompare函數會分别用上面的周遊方式1-5去周遊每一個list數組(包含不同大小list)中的list。
print開頭函數為輸出輔助函數。
測試環境為windows7 32位系統 3.2g雙核cpu 4g記憶體,java 7,eclipse -xms512m -xmx512m
最終測試結果如下:
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
compare loop performance of arraylist
-----------------------------------------------------------------------
list size | 10,000 | 100,000 | 1,000,000 | 10,000,000
for each | 1 ms | 3 ms | 14 ms | 152 ms
for iterator | 0 ms | 1 ms | 12 ms | 114 ms
for list.size() | 1 ms | 1 ms | 13 ms | 128 ms
for size = list.size() | 0 ms | 0 ms | 6 ms | 62 ms
for j-- | 0 ms | 1 ms | 6 ms | 63 ms
compare loop performance of linkedlist
list size | 100 | 1,000 | 10,000 | 100,000
for each | 0 ms | 1 ms | 1 ms | 2 ms
for iterator | 0 ms | 0 ms | 0 ms | 2 ms
for list.size() | 0 ms | 1 ms | 73 ms | 7972 ms
for size = list.size() | 0 ms | 0 ms | 67 ms | 8216 ms
for j-- | 0 ms | 1 ms | 67 ms | 8277 ms
第一張表為arraylist對比結果,第二張表為linkedlist對比結果。
表橫向為同一周遊方式不同大小list周遊的時間消耗,縱向為同一list不同周遊方式周遊的時間消耗。
ps:由于首次周遊list會稍微多耗時一點,for each的結果稍微有點偏差,将測試代碼中的幾個type順序調換會發現,for each耗時和for iterator接近。
3、周遊方式性能測試結果分析
(1) foreach介紹
foreach是java se5.0引入的功能很強的循環結構,for (integer j : list)應讀作for each int in list。
for (integer j : list)實作幾乎等價于
while(iterator.hasnext()) {
integer j = iterator.next();
下面的分析會将foreach和顯示調用集合疊代器兩種周遊方式歸類為iterator方式,其他三種稱為get方式周遊。
使用foreach結構的類對象必須實作了iterable接口,java的collection繼承自此接口,list實作了collection,這個接口僅包含一個函數,源碼如下:
package java.lang;
import java.util.iterator;
/**
* implementing this interface allows an object to be the target of
* the "foreach" statement.
*
* @param <t> the type of elements returned by the iterator
* @since 1.5
*/
public interface iterable<t> {
* returns an iterator over a set of elements of type t.
* @return an iterator.
iterator<t> iterator();
iterator()用于傳回一個iterator,從foreach的等價實作中我們可以看到,會調用這個函數得到iterator,再通過iterator的next()得到下一個元素,hasnext()判斷是否還有更多元素。iterator源碼如下:
public interface iterator<e> {
boolean hasnext();
e next();
void remove();
(2) arraylist周遊方式結果分析
從上面我們可以看出:
a. 在arraylist大小為十萬之前,五種周遊方式時間消耗幾乎一樣
b. 在十萬以後,第四、五種周遊方式快于前三種,get方式優于iterator方式,并且
用臨時變量size取代list.size()性能更優。我們看看arraylist中疊代器iterator和get方法的實作
private class itr implements iterator<e> {
int cursor; // index of next element to return
int lastret = -1; // index of last element returned; -1 if no such
int expectedmodcount = modcount;
public boolean hasnext() {
return cursor != size;
@suppresswarnings("unchecked")
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];
……
public e get(int index) {
rangecheck(index);
return elementdata(index);
從中可以看出get和iterator的next函數同樣通過直接定位資料擷取元素,隻是多了幾個判斷而已。
c . 從上可以看出即便在千萬大小的arraylist中,幾種周遊方式相差也不過50ms左右,且在常用的十萬左右時間幾乎相等,考慮foreach的優點,我們大可選用foreach這種簡便方式進行周遊。
(3) linkedlist周遊方式結果分析
a 在linkedlist大小接近一萬時,get方式和iterator方式就已經差了差不多兩個數量級,十萬時iterator方式性能已經遠勝于get方式。
我們看看linkedlist中疊代器和get方法的實作
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private class listitr implements listiterator<e> {
private node<e> lastreturned = null;
private node<e> next;
private int nextindex;
private int expectedmodcount = modcount;
listitr(int index) {
// assert ispositionindex(index);
next = (index == size) ? null : node(index);
nextindex = index;
return nextindex < size;
if (!hasnext())
lastreturned = next;
next = next.next;
nextindex++;
return lastreturned.item;
checkelementindex(index);
return node(index).item;
* returns the (non-null) node at the specified element index.
node<e> node(int index) {
// assert iselementindex(index);
if (index < (size >> 1)) {
node<e> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
node<e> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
從上面代碼中可以看出linkedlist疊代器的next函數隻是通過next指針快速得到下一個元素并傳回。而get方法會從頭周遊直到index下标,查找一個元素時間複雜度為哦o(n),周遊的時間複雜度就達到了o(n2)。
是以對于linkedlist的周遊推薦使用foreach,避免使用get方式周遊。
(4) arraylist和linkedlist周遊方式結果對比分析
從上面的數量級來看,同樣是foreach循環周遊,arraylist和linkedlist時間差不多,可将本例稍作修改加大list size會發現兩者基本在一個數量級上。
但arraylist get函數直接定位擷取的方式時間複雜度為o(1),而linkedlist的get函數時間複雜度為o(n)。
再結合考慮空間消耗的話,建議首選arraylist。對于個别插入删除非常多的可以使用linkedlist。
4、結論總結
通過上面的分析我們基本可以總結下:
(1) 無論arraylist還是linkedlist,周遊建議使用foreach,尤其是資料量較大時linkedlist避免使用get周遊。
(2) list使用首選arraylist。對于個别插入删除非常多的可以使用linkedlist。
(3) 可能在周遊list循環内部需要使用到下标,這時綜合考慮下是使用foreach和自增count還是get方式。