1. 程式人生 > >常見的集合容器應當避免的坑

常見的集合容器應當避免的坑

前言

前不久幫同事一起 review 一個 job 執行緩慢的問題時發現不少朋友在擼碼實現功能時還是有需要細節不夠注意,於是便有了這篇文章。

ArrayList 踩坑

List<string> temp = new ArrayList() ;

//獲取一批資料
List<string> all = getData();
for(String str : all) {
	temp.add(str);
}

首先大家看看這段程式碼有什麼問題嘛?

其實在大部分情況下這都是沒啥問題,無非就是迴圈的往 ArrayList 中寫入資料而已。

但在特殊情況下,比如這裡的 getData()

返回資料非常巨大時後續 temp.add(str) 就會有問題了。

比如我們在 review 程式碼時發現這裡返回的資料有時會高達 2000W,這時 ArrayList 寫入的問題就凸顯出來了。

填坑指南

大家都知道 ArrayList 是由陣列實現,而資料的長度有限;需要在合適的時機對陣列擴容。

> 這裡以插入到尾部為例 add(E e)。

ArrayList<string> temp = new ArrayList&lt;&gt;(2) ;
temp.add("1");
temp.add("2");
temp.add("3");

當我們初始化一個長度為 2 的 ArrayList ,並往裡邊寫入三條資料時 ArrayList 就得擴容了,也就是將之前的資料複製一份到新的陣列長度為 3 的陣列中。

> 之所以是 3 ,是因為新的長度=原有長度 * 1.5

通過原始碼我們可以得知 ArrayList 的預設長度為 10.

但其實並不是在初始化的時候就建立了 DEFAULT_CAPACITY = 10 的陣列。

而是在往裡邊 add 第一個資料的時候會擴容到 10.

既然知道了預設的長度為 10 ,那說明後續一旦寫入到第九個元素的時候就會擴容為 10*1.5 =15。 這一步為陣列複製,也就是要重新開闢一塊新的記憶體空間存放這 15 個數組。

一旦我們頻繁且數量巨大的進行寫入時就會導致許多的陣列複製,這個效率是極低的。

但如果我們提前預知了可能會寫入多少條資料時就可以提前避免這個問題。

比如我們往裡邊寫入 1000W 條資料,在初始化的時候就給定陣列長度與用預設 10 的長度之間效能是差距巨大的。

> 我用 JMH 基準測試驗證如下:

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
public class CollectionsTest {

    private static final int TEN_MILLION = 10000000;

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void arrayList() {

        List<string> array = new ArrayList&lt;&gt;();

        for (int i = 0; i &lt; TEN_MILLION; i++) {
            array.add("123");
        }

    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void arrayListSize() {
        List<string> array = new ArrayList&lt;&gt;(TEN_MILLION);

        for (int i = 0; i &lt; TEN_MILLION; i++) {
            array.add("123");
        }

    }


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(CollectionsTest.class.getSimpleName())
                .forks(1)
                .build();


        new Runner(opt).run();
    }
}

根據結果可以看出預設長度的效率會比用預設的效率高上很多(這裡的 Score 指執行完函式所消耗的時間)。

所以這裡強烈建議大家:在有大量資料寫入 ArrayList 時,一定要初始化指定長度。


再一個是一定要慎用 add(int index, E element) 向指定位置寫入資料。

通過原始碼我們可以看出,每一次寫入都會將 index 後的資料往後移動一遍,其實本質也是要複製陣列;

但區別於往常規的往陣列尾部寫入資料,它每次都會進行陣列複製,效率極低。

LinkedList

提到 ArrayList 就不得不聊下 LinkedList 這個孿生兄弟;雖說都是 List 的容器,但本質實現卻完全不同。

LinkedList 是由連結串列組成,每個節點又有頭尾兩個節點分別引用了前後兩個節點;因此它也是一個雙向連結串列。

所以理論上來說它的寫入非常高效,將不會有 ArrayList 中效率極低的陣列複製,每次只需要移動指標即可。

> 這裡偷懶就不畫圖了,大家自行腦補下。

對比測試

坊間一直流傳:

> LinkedList 的寫入效率高於 ArrayList,所以在寫大於讀的時候非常適用於 LinkedList 。

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void linkedList() {
        List<string> array = new LinkedList&lt;&gt;();

        for (int i = 0; i &lt; TEN_MILLION; i++) {
            array.add("123");
        }

    }

這裡測試看下結論是否符合;同樣的也是對 LinkedList 寫入 1000W 次資料,通過結果來看初始化陣列長度的 ArrayList 效率明顯是要高於 LinkedList

但這裡的前提是要提前預設 ArrayList 的陣列長度,避免陣列擴容,這樣 ArrayList 的寫入效率是非常高的,而 LinkedList 的雖然不需要複製記憶體,但卻需要建立物件,變換指標等操作。

而查詢就不用多說了,ArrayList 可以支援下標隨機訪問,效率非常高。

LinkedList 由於底層不是陣列,不支援通過下標訪問,而是需要根據查詢 index 所在的位置來判斷是從頭還是從尾進行遍歷。

但不管是哪種都得需要移動指標來一個個遍歷,特別是 index 靠近中間位置時將會非常慢。

總結

高效能應用都是從小細節一點點堆砌起來的,就如這裡提到的 ArrayList 的坑一樣,日常使用沒啥大問題,一旦資料量起來所有的小問題都會成為大問題。

所以再總結下:

  • 再使用 ArrayList 時如果能提前預測到資料量大小,比較大時一定要指定其長度。
  • 儘可能避免使用 add(index,e) api,會導致複製陣列,降低效率。
  • 再額外提一點,我們常用的另一個 Map 容器 HashMap 也是推薦要初始化長度從而避免擴容。

本文所有測試程式碼:

https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/basic/CollectionsTest.java

你的點贊與分享是對我最大的支援

相關推薦

常見集合容器應當避免

前言 前不久幫同事一起 review 一個 job 執行緩慢的問題時發現不少朋友在擼碼實現功能時還是有需要細節不夠注意,於是便有

Tomcat Jboss Glassfish 三種常見web容器比較

用戶 sil 簡單 架構 開箱 .aspx app 規模 serve https://i.cnblogs.com/EditPosts.aspx?postid=7596859 Tomcat Jboss Glassfish 三種常見web容器比較<br>一、緣由: 

List、Set、Map常見集合遍歷總結

排序 out java tlab vhdl for var 定義 word Java中的集合有三大類,List、Set、Map,都處於java.util包中,List、Set和Map都是接口,不能被實例化,它們的各自的實現類可以被實例化。List的實現類主要有ArrayLi

集合容器概覽

ret 定義排序 ger 優先 意義 刪除 技能 java類 結構   Java世界中,泛型和集合容器的存在大大的提高了程序員的編程能力,是開發者技能百寶箱中的重要武器之一,、因此掌握集合框架的實現原理及內部結構變得非常的重要。本文主要對集合框架體系中日常開發經常遇到的接口

Java常見集合的默認大小及擴容機制

數組長度 pan 減少 hashmap 代碼 這就是 整數 一段 span 在面試後臺開發的過程中,集合是面試的熱話題,不僅要知道各集合的區別用法,還要知道集合的擴容機制,今天我們就來談下ArrayList 和 HashMap的默認大小以及擴容機制。 在 Java 7 中,

01.Python基礎-3.集合容器

另一個 清空 裏的 在那 遍歷 不可 1.10 value sid 1 列表list 1.1 列表介紹 Python內置的一種數據類型是列表:list。 有序的集合,可隨時添加和刪除其中的元素。 每個元素都分配一個數字 ——它的位置,或索引。0,1,2,3…… 可存放各種

java面試/筆試題目之Java常見集合(持續更新中)

宣告:題目大部分來源於Java後端公眾號,有些個人整理,但答案皆為個人整理,僅供參考。 目錄 Java中的集合 List 和 Set 區別 1.Set:集合中的物件不按特定方式排序(針對記憶體地址來說,即非線性),並且沒有重複物件。它的有些實現類能對集合中的物件按特定方式排序。

四、multiset多重集合容器與set集合容器的區別

簡介:multiset與set大體上是一樣的,唯一不同的是,multiset允許重複的元素鍵值插入,set是不允許的。關於set集合容器的詳細內容看:https://blog.csdn.net/ysz171360154/article/details/84142947    mu

三、set集合容器-遍歷刪除查詢與自定義

簡介:要學習set集合容器,首先要了解紅黑樹(Red-black Tree)。紅黑樹是一種自平衡二叉查詢樹,是電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。Set集合容器實現了紅黑樹的平衡二叉檢索樹的資料結構,在插入元素時,它會自動調整二叉樹的排列,把該元素放到適當的位置,以確保每個子樹根

三年的python開發經驗,總結出這【30個常見錯誤】,避免重蹈覆轍!!!

導讀:在這篇文章中,我將總結新老Python程式設計師常犯的一些錯誤,以幫助你們在自己的工作避免犯同樣或類似錯誤。 在這篇文章中,我將總結新老Python程式設計師常犯的一些錯誤,以幫助你們在自己的工作避免犯同樣或類似錯誤。 首先我要說明一下的是,這些都是來源於第一手的經驗。我以講授Python的知識為生

面試系列:常見容器list和map簡單介紹

Android 列表資料結構一般常用兩種ArrayList和LinkedList 兩種列表結構主要是根據不同的需求選用。 ArrayList的底層是陣列結構,多用於查詢。這應該也是我們最長使用的資料結構了,因為android中的列表展示資料過於多,配合ListView和R

multiset多重集合容器

multiset與set一樣,唯一的不同是:multiset允許重複元素的鍵值插入,而set不允許 同樣是set標頭檔案 multiset元素插入(輸入多個以後,輸出按照從小到大的順序) #include <iostream> #include <set> #in

Fabric CLI容器啟動的

這個坑是在使用鏈碼的時候發現的,第一次成功了,等到下次再想使用的時候發現cli容器啟動不了(啟動後又馬上關閉了) 下面是截的一段錯誤: 我是通過下面程式碼來啟動整個開發者模式,其中包括cli容器啟動 docker-compose -f docker-compose-simple.ya

第4章 庫存管理案例(加入集合容器ArrayList)

第4章 庫存管理案例 4.1 案例介紹 現在,我們將原有的庫存管理案例,採用更好的集合方式實現。 將對下列功能進行方法封裝:  列印庫存清單功能  庫存商品數量修改功能  退出程式功能 4.2 案例需求分析 管理員能夠進行的操作有3項(檢視、修改、退出)

Java常見集合框架(十六):Queue之DelayQueue、PriorityQueue、PriorityBlockingQueue

DelayQueue public class DelayQueue extends AbstractQueue implements BlockingQueue Delayed 元素的一個基於優先順序的無界阻塞佇列,只有在延遲期滿時才能從中提取元

使用Docker容器應該避免的10個事情

當你最後投入容器的懷抱,發現它能解決很多問題,而且還具有眾多的 優點:

(vue2.0 案例3.0) 在vue-cli 專案中 需要知道常見的配置 防止入

一、配置打包後的檔案路徑  進入config>index.js把assetsPublicPath:'/'改成'./'; build: { env: require('./prod.env'), index: path.resolve(__dirname, '..

常見集合的迴圈輸出方式

一、List集合的迴圈輸出 List<Object> objList = new ArrayList<Object>();  1) for (int i=0; i<objList.size; i++)     {  // 迴圈輸出集合中的每個物

有關Kubernetes監控的4大常見陷阱,注意避免

Kubernetes(K8S)現在似乎是管理和部署基於微服務和容器的應用程式的事實標準——其中緣由亦不難理解。Kubernetes是最大的開源社群,它由雲原生計算基金會(CNCF)支援,它是DevOps友好的,它提供了混合雲的優勢。有什麼理由不愛它? 但在TheNewStack的最近一項調查中,69%的

ArrayList等常見集合的排序問題

對於ArrayList等常用的集合具體業務類,基本上都實現了Comparable介面,即可以用來比較裝載的物件實體。 主要用Collections.sort方法對集合類中的物件進行排序 Collections.sort的兩種過載方法 Collecti