To Java程式員:切勿用普通for迴圈遍歷LinkedList

来源:http://www.cnblogs.com/xrq730/archive/2016/02/14/5189565.html
-Advertisement-
Play Games

ArrayList與LinkedList的普通for迴圈遍歷 對於大部分Java程式員朋友們來說,可能平時使用得最多的List就是ArrayList,對於ArrayList的遍歷,一般用如下寫法: public static void main(String[] args) { List<Integ


ArrayList與LinkedList的普通for迴圈遍歷

對於大部分Java程式員朋友們來說,可能平時使用得最多的List就是ArrayList,對於ArrayList的遍歷,一般用如下寫法:

public static void main(String[] args)
{
    List<Integer> arrayList = 
            new ArrayList<Integer>();
    
    for (int i = 0; i < 100; i++)
        arrayList.add(i);
    for (int i = 0; i < 100; i++)
        System.out.println(arrayList.get(i));
}

如果以後要用到LinkedList了,可能有些朋友就會用一樣的方式去遍歷LinkedList了:

public static void main(String[] args)
{
    List<Integer> linkedList = 
            new LinkedList<Integer>();
    
    for (int i = 0; i < 100; i++)
        linkedList.add(i);
    for (int i = 0; i < 100; i++)
        System.out.println(linkedList.get(i));
}

請記住:這是一種非常糟糕的做法。這其實已經不是Java的問題,而是數據結構的問題了,我相信語言從Java換成其他的也都一樣。

下麵對ArrayList和LinkedList的普通for迴圈效率進行測試以及分析原因。

 

ArrayList和LinkedList使用普通for迴圈遍歷速度對比

先給出測試代碼:

public class ListIteratorTest
{
    private final static int LIST_SIZE = 1000;
    
    public static void main(String[] args)
    {
        List<Integer> arrayList = 
                new ArrayList<Integer>();
        List<Integer> linkedList = 
                new LinkedList<Integer>();
        
        for (int i = 0; i < LIST_SIZE; i++)
        {
            arrayList.add(i);
            linkedList.add(i);
        }
        
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < arrayList.size(); i++)
            arrayList.get(i);
        System.out.println("ArrayList遍歷速度:" + (System.currentTimeMillis() - startTime) + "ms");
        
        startTime = System.currentTimeMillis();
        for (int i = 0; i < linkedList.size(); i++)
            linkedList.get(i);
        System.out.println("LinkedList遍歷速度:" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

不斷增大LIST_SIZE,我用表格表示一下運行結果:

  1000 5000 10000 50000 100000
ArrayList 0ms 1ms 2ms 3ms 3ms
LinkedList 3ms 16ms 88ms 2446ms 18848ms

從運行結果我們看到,按倍數增大List容量,ArrayList的遍歷顯得比較穩定,而LinkedList的遍歷幾乎是爆髮式的增長,再測試下去已經沒有必要了。

下麵解釋一下產生此現象的原因。

 

ArrayList使用普通for迴圈遍歷快的原因

先看一下ArrayList的get方法源代碼:

public E get(int index) {
RangeCheck(index);

return (E) elementData[index];
}

看到ArrayList的get方法只是從數組裡面拿一個位置上的元素罷了。我們有結論,ArrayList的get方法的時間複雜度是O(1),O(1)的意思也就是說時間複雜度是一個常數,和數組的大小並沒有關係,只要給定數組的位置,直接就能定位到數據。

其實熟悉C、C++或者對指針理解的朋友一定很好理解為什麼,我解釋一下為什麼對數組使用get就快。

在電腦底層,數據都是有地址的,就像人有住址一樣。假設我寫了這麼一句代碼:

int[3] ints = {1, 3, 5};

在Java中一個int型數據是4個位元組,此時電腦內部做的事情是,在記憶體空間中找到一塊連續的、足以存放3個4位元組也就是12位元組的數組的記憶體空間,並返回該記憶體空間的首地址。比方說該記憶體空間的首地址是0x00吧,那麼那麼1就放在0x00~0x03中、3就放在0x04~0x07中、5就放在0x08~0x0B中。

這時就很簡單了,取ints[1]的時候,電腦就會算出ints[1]的數據是存放在以0x04開頭,占據4個位元組空間的記憶體中,因此,電腦會從0x04~0x07這塊地址空間中讀取數據出來。

整個過程,和數組有多大,並沒有關係,電腦做的只是算出起始地址-->去該地址中取數據而已,因此我們看到使用普通for迴圈遍歷ArrayList的速度很快,也很穩定。

 

LinkedList使用普通for迴圈遍歷慢的原因

再看一下LinkedList的get方法做了什麼:

public E get(int index) {
    return entry(index).element;
}
 1 private Entry<E> entry(int index) {
 2     if (index < 0 || index >= size)
 3         throw new IndexOutOfBoundsException("Index: "+index+
 4                                             ", Size: "+size);
 5     Entry<E> e = header;
 6     if (index < (size >> 1)) {
 7         for (int i = 0; i <= index; i++)
 8             e = e.next;
 9     } else {
10         for (int i = size; i > index; i--)
11             e = e.previous;
12     }
13     return e;
14 }

由於LinkedList是雙向鏈表,因此第6行的意思是算出i在一半前還是一半後,一半前正序遍歷、一半後倒序遍歷,這樣會快很多,當然,先不管這個,分析一下為什麼使用普通for迴圈遍歷LinkedList會這麼慢。

原因就在第7~第8行,第10~第11行的兩個for循裡面,以前者為例:

1、get(0),直接拿到0位的Node0的地址,拿到Node0裡面的數據

2、get(1),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,拿到Node1裡面的數據

3、get(2),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,從1位的Node1中找到下一個2位的Node2的地址,找到Node2,拿到Node2裡面的數據。

後面的以此類推。

也就是說,LinkedList在get任何一個位置的數據的時候,都會把前面的數據走一遍。假如我有10個數據,那麼將要查詢1+2+3+4+5+5+4+3+2+1=30次數據,相比ArrayList,卻只需要查詢10次數據就行了,隨著LinkedList的容量越大,差距會越拉越大。其實使用LinkedList到底要查詢多少次數據,大家應該已經很明白了,就是容量一半的階乘,即(0.5N)!,N為LinkedList的容量

換句話說,(0.5N)!其時間複雜度為N!,時間複雜度有以下經驗規則:

O(1) < O(log2N) < O(n) < O(N * log2N) < O(N2) < O(N3) < 2N < 3N < N!

前四個比較好、中間兩個一般、後3個很爛。也就是說N!是最糟糕的一種時間複雜度了,只要N比較大,程式基本就跑不動了。

 

後記

根據以上的分析,各位Java程式員朋友們,切記一定不要使用普通for迴圈去遍歷LinkedList。使用迭代器或者foreach迴圈(foreach迴圈的原理就是迭代器)去遍歷LinkedList即可,這種方式是直接按照地址去找數據的,將會大大提升遍歷LinkedList的效率。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 背景 前幾天有同事問到我一個簡單的功能, 就是當你使用枚舉時如何給每個一元素增加描述字元串並且可以很容易的讀取出來. 比如有一個枚舉類型是列出對一個問題給出的選項(例如: 同意?不同意?中立?): public enum AssessmentAnswer { Strongly_Disagree =
  • 前言 現在,經驗證的 DreamSpark 學生無需承擔任何責任即可免費獲取 Microsoft Azure for DreamSpark,且沒有時間限制和意外費用。如果需要,您隨後可升級獲取更多服務,但您現在即可藉助背後 Microsoft 雲的強大功能托管您的 Web 應用和網站,且無需花費任何
  • 信號的概念 信號(signal)-- 進程之間通訊的方式,是一種軟體中斷。一個進程一旦接收到信號就會打斷原來的程式執行流程來處理信號。 幾個常用信號: SIGINT 終止進程 中斷進程 (control+c) SIGTERM 終止進程 軟體終止信號 SIGKILL 終止進程 殺死進程 SIGALRM
  • 別人的項目,剛用MyEclipse載入進來,一大堆錯誤(見怪不怪了) JSP報錯,上圖: 報錯:“The method getContextPath() from the type HttpServletRequest refers to the missing type String” 解決方式:
  • 又一個milestone即將結束,有了些許的時間總結研發過程中的點滴心得,今天總結下如何在編寫python代碼時對非同步操作進行同步化模擬,從而提高代碼的可讀性和可擴展性。 游戲引擎一般都採用分散式框架,通過一定的策略來均衡伺服器集群的資源負載,從而保證伺服器運算的高併發性和CPU高利用率,最終提高游
  • 事件模型是被廣泛使用的好東西,但是C++標準庫里沒有現成的,其他實現又複雜或者不優雅,比如需要使用巨集。現在VC11可以用在XP下了,那麼就痛快的拿起C++11提供的先進設施組合出一個輕便的實現吧。 為了達到簡潔的目的,需要放棄一些特性: 1、不支持判斷函數是否已經綁定過(因為std::functio
  • 清除空格的方法是不安全的,部分原因是因為字元中的空格非常多,例如 "addslashes的問題在 於黑客 可以用0xbf27來代替單引號,而addslashes只是將0xbf27修改為0xbf5c27,成為一個有效的多位元組字元,其中的0xbf5c仍會 被看作是單引號,所以addslashes無法成功
  • 以下是小白的爬蟲學習歷程中遇到並解決的一些困難,希望寫出來給後來人,如有疏漏懇請大牛指正,不勝感謝! 首先,我的代碼是這樣的 1 2 3 import requests 4 5 url = 'http://www.acfun.tv/' 6 html = requests.get(url) 7 8 p
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...