摘要:盡可能避免使用,會導致復制數組,降低效率。再額外提一點,我們常用的另一個容器也是推薦要初始化長度從而避免擴容。
前言
前不久幫同事一起 review 一個 job 執行緩慢的問題時發現不少朋友在擼碼實現功能時還是有需要細節不夠注意,于是便有了這篇文章。
ArrayList 踩坑Listtemp = new ArrayList() ; //獲取一批數據 List all = getData(); for(String str : all) { temp.add(str); }
首先大家看看這段代碼有什么問題嘛?
其實在大部分情況下這都是沒啥問題,無非就是循環的往 ArrayList 中寫入數據而已。
但在特殊情況下,比如這里的 getData() 返回數據非常巨大時后續 temp.add(str) 就會有問題了。
比如我們在 review 代碼時發現這里返回的數據有時會高達 2000W,這時 ArrayList 寫入的問題就凸顯出來了。
填坑指南大家都知道 ArrayList 是由數組實現,而數據的長度有限;需要在合適的時機對數組擴容。
這里以插入到尾部為例 add(E e)。
ArrayListtemp = new ArrayList<>(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() { Listarray = new ArrayList<>(); for (int i = 0; i < TEN_MILLION; i++) { array.add("123"); } } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public void arrayListSize() { List array = new ArrayList<>(TEN_MILLION); for (int i = 0; i < 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() { Listarray = new LinkedList<>(); for (int i = 0; i < 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
你的點贊與分享是對我最大的支持
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75161.html
摘要:第二具備輕量化特性容器的體積非常小巧。他們大多認為自己應該將應用程序部署至當前正在運行的容器當中。不要創建大型鏡像體積過大的鏡像會加大其發布難度。總體來講,在向生產環境中部署容器時,必須避免使用最新標簽。 當下最火爆的Docker,是一個開源的應用容器引擎。大家已經開始認同并接受容器技術,并意識到它能夠解決多種現實問題并具備一系列無可比擬的優勢。今天小數就和大家聊一聊容器技術的優勢和誤...
摘要:嵌套對象成員會造成重大性能影響盡量少用。一般來說你可以通過這種方法提高代碼的性能將經常使用的對象成員數組項和域外變量存入局部變量中。在反復訪問的地方使用局部變量存放引用小心地處理集合因為他們表現出存在性總是對底層文檔重新查詢。 前言 本期我來給大家推薦的書是《高性能JavaScript》,在這本書中我們能夠了解 javascript 開發過程中的性能瓶頸,如何提升各方面的性能,包括代碼...
摘要:都會造成錯誤,注意一定一定嚴格的用,所以我建議直接復制我的。因為用的話他會轉義代碼,寫不寫其實一個樣。不可避免的,構建肯定是要用到的。這個時候一般用的是在外面保存然后里面調用第二個坑更隱蔽。 目標人群 獻給熟悉基礎的React語法的剛接觸React的同學~ 如果你已經寫過半年以上的React那也不用看了,畢竟我水平并不高 Whats React React 是一個不存在的網絡公司Fac...
摘要:本文章旨在總結開發過程中碰到的容易忘記或者比較重要的坑,一方面加深自己對于該部分的理解,另一方面希望能夠分享給大家,知識在于分享,當然踩過的坑也不例外滑稽。 在日常工作中,時常會碰到各種各樣的坑,有時真的覺得很多時候開發的經驗都是踩坑踩出來的。在通往大牛的道路上,希望自己能夠跨越重重阻礙,越走越遠。學會時常總結,不斷提升自己。 本文章旨在總結開發過程中碰到的容易忘記或者比較重要的坑,...
閱讀 2832·2023-04-25 18:58
閱讀 977·2021-11-25 09:43
閱讀 1210·2021-10-25 09:46
閱讀 3494·2021-09-09 11:40
閱讀 1679·2021-08-05 09:59
閱讀 869·2019-08-29 15:07
閱讀 956·2019-08-29 12:48
閱讀 695·2019-08-29 11:19