Java中普通的遍歷方式一般常用的就是fori和foreach方式,在一般情況下這兩種區別不大,往往是效率區別和有一些特殊場合注意問題,下次再詳解,這次先描述關於LinkedList遍歷時遇到的問題。
具體問題:
項目中需要實現接收對方頻繁發送過來的數據並解析后序列化文件到目的服務器,采用了定量發送的辦法,每次把接收的數據解析成功后放入到LinkedList當中,當達到目標數量時,遍歷LinkedList中的數據,拼接成功想要的內容,然后序列化到目的服務器中。剛開始遍歷的方法是這樣的:
1 for (int i = 0; i < linkedList.size(); i++) { 2 linkedList.get(i); // 具體操作略,只表現遍歷方式 3 }
咋一看沒什么問題,開始測試時目標為1w也沒什么問題,后面加大目標數量到10w時有明顯卡頓,程序一直停頓在遍歷中,一時沒明白是為什么ps一起一直用ArrayList是這種遍歷方式沒出現過問題,仔細查看了一下LinkedList的數據結構發現了問題所在:
LinkedList的數據結構是雙向鏈表,在進行get方式遍歷的時候采用的方式如下:
1 public E get(int index) { 2 checkElementIndex(index); 3 return node(index).item; 4 }
checkElementIndex方法不用看具體實現,目的是判斷當前元素是在鏈表前半段或者后半段然后決定從哪邊遍歷,后面的node方法決定了具體取元素的過程實現,源碼如下:
1 Node<E> node(int index) { 2 // assert isElementIndex(index); 3 4 if (index < (size >> 1)) { 5 Node<E> x = first; 6 for (int i = 0; i < index; i++) 7 x = x.next; 8 return x; 9 } else { 10 Node<E> x = last; 11 for (int i = size - 1; i > index; i--) 12 x = x.prev; 13 return x; 14 } 15 }
從代碼中看出,在以這種方法在遍歷取元素的時候,無論目標元素在哪,都會從頭部或者尾部遍歷到目標節點然后取出。這種方式在鏈表遍歷中其實是不太合理的,試想一下簡單的遍歷,加入有10個元素的一個鏈表需要遍歷,那么每次元素的查詢次數為
[1,2,3,4,5,5,4,3,2,1]總次數為30,時間復雜度計算方式為(n*1+(n/2)*(n/2-1)*1/1)*2漸進時間復雜度即n趨向於無窮大時約等於O(n^2).當目標數量n越大時,時間復雜度的增長也就越快,從而導致了程序假死。
分析完問題,那么來查看一下如何解決問題,不能使用fori遍歷那應該采用什么方式遍歷比較好呢,下面采用了3種方式遍歷一個10000個元素的鏈表,比較了不同方式的時間花費:

public class TestLinkedList { public static void main(String[] args) { LinkedList<Integer> linkedList = getList(); iterateByFori(linkedList); iterateByForEach(linkedList); iterateByIterator(linkedList); } private static LinkedList<Integer> getList(){ LinkedList<Integer> linkedList = new LinkedList<>(); for (int i = 0; i < 100000; i++) { linkedList.add(i); } return linkedList; } //fori方式 private static void iterateByFori(LinkedList<Integer> linkedList){ long time1 = System.currentTimeMillis(); for (int i = 0; i < linkedList.size(); i++) { linkedList.get(i); } long time2 = System.currentTimeMillis(); System.out.println("Fori方式遍歷的時間花費為:"+(time2-time1)); } //foreach方式 private static void iterateByForEach(LinkedList<Integer> linkedList){ long time1 = System.currentTimeMillis(); for (Integer j:linkedList) { //TODO } long time2 = System.currentTimeMillis(); System.out.println("foreach方式遍歷的時間花費為:"+(time2-time1)); } //Iterator方式 private static void iterateByIterator(LinkedList<Integer> linkedList){ long time1 = System.currentTimeMillis(); Iterator<Integer> iterator = linkedList.iterator(); while (iterator.hasNext()){ iterator.next(); } long time2 = System.currentTimeMillis(); System.out.println("Iterator方式遍歷的時間花費為:"+(time2-time1)); } }
分別一次采用了fori方式、foreach方式以及Iterator方式遍歷(自帶的removeFirst等方法在這里不做討論),結果如下:
從圖中可以可以看出時間花費的差別大小,所以在鏈表結構實現的數據集合中,最好采用Iterator或者foreach的方式遍歷,效率最高。