java 在数组末尾添加元素_Java快问快答:用 ArrayList 还是 LinkedList?
問題:
通常我會這么定義列表:
List<String> names = new ArrayList<>()names類型使用List接口,那么具體實現該如何選擇。
什么時候應該用LinkedList替代ArrayList,反之亦然?
這里大家可以關注一下我的個人專欄《Java 進階集中營》,每天會給大家即時分享一個最新的java技術資訊,有優秀的java技術內容,也歡迎分享在我的專欄。
JAVA 進階集中營?zhuanlan.zhihu.com總結:
大多數情況下,相比LinkedList更推薦使用ArrayList或ArrayDeque。如果不確定,可以直接選用ArrayList。
LinkedList和ArrayList是List接口的兩種不同實現。LinkedList采用雙向鏈表實現。ArrayList通過動態調整數組大小實現。
與標準鏈表和數組操作一樣,不同的實現方法算法運行時也不同。
對于LinkedList<E>
- get(int index)復雜度為O(n)(平均步長n/4)
- add(E element)復雜度為O(1)
- add(int index, E element)復雜度為O(n)(平均步長n/4)。但是當index = 0時復雜度為O(1)<--- LinkedList<E>的主要優點。
- remove(int index)復雜度為O(n)(平均步長n/4)
- Iterator.remove()復雜度為O(1)。<---LinkedList<E>的主要優點
- ListIterator.add(E element)復雜度為O(1)。這是LinkedList<E>的一個主要優點。
注意:許多操作平均需要n/4步長,最好的情況下(例如index= 0)步長為常數,最壞的情況下需要n/2步(列表中間)。
對于ArrayList<E>
- get(int index)復雜度為O(1)<--- ArrayList<E>的主要優點
- add(E element)分攤后的復雜度為O(1),但最壞的情況是O(n),因為需要調整數組大小并進行拷貝
- add(int index,E element)復雜度為O(n)(平均步長n/2)
- remove(int index)復雜度為O(n)(平均步長n/2)
- Iterator.remove()復雜度為O(n)(平均步長n/2)
- ListIterator.add(E element)復雜度為O(n)(平均步長n/2)
注意:許多操作要求平均步長為n/2,最好情況下(列表末尾)步長為常數,最壞情況下(列表開始)需要n步
LinkedList<E>可以使用iterator實現固定時間插入或刪除,但只能順序訪問元素。換句話說,可以向前或向后遍歷列表,但是在列表中查找固定位置元素花費的時間與列表大小成正比。Javadoc中這么寫道:“在列表中建立索引,會從列頭或列尾開始遍歷,從更靠近的位置開始”,這些方法平均復雜度為O(n)(平均步長n/4),盡管index = 0時復雜度為O(1)。
另一方面,ArrayList<E>支持快速隨機讀取訪問,因此獲取任何元素都能在恒定時間內完成。但是,除了列尾在其它任何位置添加或刪除元素,都需要把后面的所有元素移位。同樣,如果添加的元素多于底層數組的容量,則會分配一個新數組(大小是之前的1.5倍),并把舊數組復制到新數組中。因此在ArrayList中添加元素時間復雜度最差為O(n),平均情況下為常數。
因此,根據您打算執行的操作選擇對應的實現。遍歷這兩種List開銷都很小。(從技術上看ArrayList更快,但除非確實對性能要求十分敏感,否則不必擔心。遍歷的復雜度都是常量)
使用LinkedList其中一個好處可以重用已有iterator插入和刪除元素。然后修改本地列表即可,操作的時間復雜度為O(1)。在ArrayList中,數組余下的部分需要移動(即拷貝)。而在LinkedList中執行seek操作遍歷,最壞時間復雜度為O(n)(平均步長n/2),而ArrayList中,可以直接計算位置進行訪問,復雜度為O(1)。
在LinkedList列頭增加或刪除操作時間復雜度為O(1),而ArrayList需要O(n)。請注意:ArrayDeque可以用來替代LinkedList,適合在列頭添加和刪除元素,但它不是List。
另外,如果列表很大,請記住,內存使用情況也有所不同。每個LinkedList元素都有額外開銷,因為里面還存儲了指向下一個和上一個元素的指針。ArrayLists沒有這種開銷。但是,無論是否實際添加了元素,ArrayList都會分配初始容量大小的內存。
ArrayList默認初始容量很小(Java 1.4-1.8中設為10)。但是由于底層實現是數組,因此如果添加很多元素,則必須調整數組大小。如果提前知道需要添加很多元素,為避免調整數組大小帶來的開銷,在創建ArrayList時需要設置更大的初始容量。
答案2(案例分析):
現代計算機體系結構中,ArrayList性能幾乎在所有情況下都得到大大提升。因此,除非一些非常獨特和極端的情況,應避免使用LinkedList。
從理論上講,LinkedList的add(E element)時間復雜度為O(1)。
同樣,在列表中間添加元素應該非常有效。
實際并非如此,因為LinkedList是一種對緩存不友好的數據結構。從性能的角度看,只有在極少數情況下,LinkedList的性能會優于緩存友好的ArrayList。
下面是在隨機位置插入元素的基準測試結果。就像你看到的那樣:ArrayList的效率要高得多。盡管從理論上講,每次向列表中插入元素都需要“移動”數組中后續n個后元素(個數越少越好):
在緩存更大、速度更快的下一代硬件上運行,得出的結論更明確:
LinkedList完成相同工作所需的時間更長。
有兩個主要原因:
DynamicIntArray是一個自定義ArrayList實現,元素類型為Int(原始類型)而非Object。因此所有數據實際上都是相鄰存儲,因此效率更高。
記住一個關鍵因素,獲取存儲塊比訪問單個存儲單元的開銷更大。這就是為什么讀取器1MB順序內存要比從不同內存塊中讀取同樣的數據量快400倍的原因:
延遲數據比較(?2012) ---------------------------------- L1 cache reference 0.5 ns Branch mispredict 5 ns L2 cache reference 7 ns 14x L1 cache Mutex lock/unlock 25 ns Main memory reference 100 ns 20x L2 cache, 200x L1 cache Compress 1K bytes with Zippy 3,000 ns 3 us Send 1K bytes over 1 Gbps network 10,000 ns 10 us Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD Read 1 MB sequentially from memory 250,000 ns 250 us Round trip within same datacenter 500,000 ns 500 us Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms來源:每個程序員都應該知道的延遲數據
為了讓觀點表達得更清晰,可以查看列表開頭的add element結果。從理論上講,這只是一種情況。其實LinkedList應該表現得更好,而ArrayList的結果應該不及它:
注意:這是C++ Std庫的基準測試。但是根據我之前的經驗,C++和Java的結果非常類似。源代碼
復制連續內存是現代CPU的一種優化:理論在不斷演變,實際上又讓ArrayList和Vector效率變得更高。
參考文中發布的所有基準測試均來自KjellHedstr?m。在他的博客上可以找到更多數據。
博客地址:https://kjellkod.wordpress.com/
總結
以上是生活随笔為你收集整理的java 在数组末尾添加元素_Java快问快答:用 ArrayList 还是 LinkedList?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js实现将数据导出为excel文件
- 下一篇: .NET平台功能最强大,性能最佳的JSO