Java初學者必備 Java集合的小抄

2021-02-21 藍橋雲課精選

在儘可能短的篇幅裡,將所有集合與並發集合的特徵,實現方式,性能捋一遍。適合所有」精通Java」其實還不那麼自信的人閱讀。

List

◆ArrayList

以數組實現。節約空間,但數組有容量限制。超出限制時會增加50%容量,用System.arraycopy()複製到新的數組,因此最好能給出數組大小的預估值。默認第一次插入元素時創建大小為10的數組。

按數組下標訪問元素–get(i)/set(i,e) 的性能很高,這是數組的基本優勢。

直接在數組末尾加入元素–add(e)的性能也高,但如果按下標插入、刪除元素–add(i,e), remove(i), remove(e),則要用System.arraycopy()來移動部分受影響的元素,性能就變差了,這是基本劣勢。

◆LinkedList

以雙向鍊表實現。鍊表無容量限制,但雙向鍊表本身使用了更多空間,也需要額外的鍊表指針操作。

按下標訪問元素–get(i)/set(i,e) 要悲劇的遍歷鍊表將指針移動到位(如果i>數組大小的一半,會從末尾移起)。

插入、刪除元素時修改前後節點的指針即可,但還是要遍歷部分鍊表的指針才能移動到下標所指的位置,只有在鍊表兩頭的操作–add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指針的移動。

◆CopyOnWriteArrayList

並發優化的ArrayList。用CopyOnWrite策略,在修改時先複製一個快照來修改,改完再讓內部指針指向新數組。

因為對快照的修改對讀操作來說不可見,所以只有寫鎖沒有讀鎖,加上複製的昂貴成本,典型的適合讀多寫少的場景。如果更新頻率較高,或數組較大時,還是Collections.synchronizedList(list),對所有操作用同一把鎖來保證線程安全更好。

增加了addIfAbsent(e)方法,會遍歷數組來檢查元素是否已存在,性能可想像的不會太好。

◆補充

無論哪種實現,按值返回下標–contains(e), indexOf(e), remove(e) 都需遍歷所有元素進行比較,性能可想像的不會太好。

沒有按元素值排序的SortedList,在線程安全類中也沒有無鎖算法的ConcurrentLinkedList,湊合著用Set與Queue中的等價類時,會缺少一些List特有的方法。

Map

◆HashMap

以Entry[]數組實現的哈希桶數組,用Key的哈希值取模桶數組的大小可得到數組下標。

插入元素時,如果兩條Key落在同一個桶(比如哈希值1和17取模16後都屬於第一個哈希桶),Entry用一個next屬性實現多個Entry以單向鍊表存放,後入桶的Entry將next指向桶當前的Entry。

查找哈希值為17的key時,先定位到第一個哈希桶,然後以鍊表遍歷桶裡所有元素,逐個比較其key值。

當Entry數量達到桶數量的75%時(很多文章說使用的桶數量達到了75%,但看代碼不是),會成倍擴容桶數組,並重新分配所有原來的Entry,所以這裡也最好有個預估值。

取模用位運算(hash & (arrayLength-1))會比較快,所以數組的大小永遠是2的N次方, 你隨便給一個初始值比如17會轉為32。默認第一次放入元素時的初始值是16。

iterator()時順著哈希桶數組來遍歷,看起來是個亂序。

在JDK8裡,新增默認為8的閥值,當一個桶裡的Entry超過閥值,就不以單向鍊表而以紅黑樹來存放以加快Key的查找速度。

◆LinkedHashMap

擴展HashMap增加雙向鍊表的實現,號稱是最佔內存的數據結構。支持iterator()時按Entry的插入順序來排序(但是更新不算, 如果設置accessOrder屬性為true,則所有讀寫訪問都算)。

實現上是在Entry上再增加屬性before/after指針,插入時把自己加到Header Entry的前面去。如果所有讀寫訪問都要排序,還要把前後Entry的before/after拼接起來以在鍊表中刪除掉自己。

◆TreeMap

以紅黑樹實現,篇幅所限詳見入門教程。支持iterator()時按Key值排序,可按實現了Comparable接口的Key的升序排序,或由傳入的Comparator控制。可想像的,在樹上插入/刪除元素的代價一定比HashMap的大。

支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。

◆ConcurrentHashMap

並發優化的HashMap,默認16把寫鎖(可以設置更多),有效分散了阻塞的概率,而且沒有讀鎖。


數據結構為Segment[],Segment裡面才是哈希桶數組,每個Segment一把鎖。Key先算出它在哪個Segment裡,再算出它在哪個哈希桶裡。

支持ConcurrentMap接口,如putIfAbsent(key,value)與相反的replace(key,value)與以及實現CAS的replace(key, oldValue, newValue)。

沒有讀鎖是因為put/remove動作是個原子動作(比如put是一個對數組元素/Entry 指針的賦值操作),讀操作不會看到一個更新動作的中間狀態。

◆ConcurrentSkipListMap

JDK6新增的並發優化的SortedMap,以SkipList實現。SkipList是紅黑樹的一種簡化替代方案,是個流行的有序集合算法,篇幅所限見入門教程。Concurrent包選用它是因為它支持基於CAS的無鎖算法,而紅黑樹則沒有好的無鎖算法。

很特殊的,它的size()不能隨便調,會遍歷來統計。

◆補充

關於null,HashMap和LinkedHashMap是隨意的,TreeMap沒有設置Comparator時key不能為null;ConcurrentHashMap在JDK7裡value不能為null(這是為什麼呢?),JDK8裡key與value都不能為null;ConcurrentSkipListMap是所有JDK裡key與value都不能為null。

Set

Set幾乎都是內部用一個Map來實現, 因為Map裡的KeySet就是一個Set,而value是假值,全部使用同一個Object。Set的特徵也繼承了那些內部Map實現的特徵。

◆HashSet

內部是HashMap。

◆LinkedHashSet

內部是LinkedHashMap。

◆TreeSet

內部是TreeMap的SortedSet。

◆ConcurrentSkipListSet

內部是ConcurrentSkipListMap的並發優化的SortedSet。

◆CopyOnWriteArraySet

內部是CopyOnWriteArrayList的並發優化的Set,利用其addIfAbsent()方法實現元素去重,如前所述該方法的性能很一般。

◆補充

好像少了個ConcurrentHashSet,本來也該有一個內部用ConcurrentHashMap的簡單實現,但JDK偏偏沒提供。Jetty就自己封了一個,Guava則直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 實現。

Queue

Queue是在兩端出入的List,所以也可以用數組或鍊表來實現。

–普通隊列–

◆LinkedList

是的,以雙向鍊表實現的LinkedList既是List,也是Queue。它是唯一一個允許放入null的Queue。

◆ArrayDeque

以循環數組實現的雙向Queue。大小是2的倍數,默認是16。

普通數組只能快速在末尾添加元素,為了支持FIFO,從數組頭快速取出元素,就需要使用循環數組:有隊頭隊尾兩個下標:彈出元素時,隊頭下標遞增;加入元素時,如果已到數組空間的末尾,則將元素循環賦值到數組[0](如果此時隊頭下標大於0,說明隊頭彈出過元素,有空位),同時隊尾下標指向0,再插入下一個元素則賦值到數組[1],隊尾下標指向1。如果隊尾的下標追上隊頭,說明數組所有空間已用完,進行雙倍的數組擴容。

◆PriorityQueue

用二叉堆實現的優先級隊列,詳見入門教程,不再是FIFO而是按元素實現的Comparable接口或傳入Comparator的比較結果來出隊,數值越小,優先級越高,越先出隊。但是注意其iterator()的返回不會排序。

–線程安全的隊列–

◆ConcurrentLinkedQueue/ConcurrentLinkedDeque

無界的並發優化的Queue,基於鍊表,實現了依賴於CAS的無鎖算法。

ConcurrentLinkedQueue的結構是單向鍊表和head/tail兩個指針,因為入隊時需要修改隊尾元素的next指針,以及修改tail指向新入隊的元素兩個CAS動作無法原子,所以需要的特殊的算法,篇幅所限見入門教程。

◆PriorityBlockingQueue

無界的並發優化的PriorityQueue,也是基於二叉堆。使用一把公共的讀寫鎖。雖然實現了BlockingQueue接口,其實沒有任何阻塞隊列的特徵,空間不夠時會自動擴容。

◆DelayQueue

內部包含一個PriorityQueue,同樣是無界的。元素需實現Delayed接口,每次調用時需返回當前離觸發時間還有多久,小於0表示該觸發了。


pull()時會用peek()查看隊頭的元素,檢查是否到達觸發時間。ScheduledThreadPoolExecutor用了類似的結構。

–線程安全的阻塞隊列–

BlockingQueue的隊列長度受限,用以保證生產者與消費者的速度不會相差太遠,避免內存耗盡。隊列長度設定後不可改變。當入隊時隊列已滿,或出隊時隊列已空,不同函數的效果見下表:


可能報異常返回布爾值可能阻塞等待可設定等待時間入隊add(e)offer(e)put(e)offer(e, timeout, unit)出隊remove()poll()take()poll(timeout, unit)查看element()peek()無無

◆ArrayBlockingQueue

定長的並發優化的BlockingQueue,基於循環數組實現。有一把公共的讀寫鎖與notFull、notEmpty兩個Condition管理隊列滿或空時的阻塞狀態。

◆LinkedBlockingQueue/LinkedBlockingDeque

可選定長的並發優化的BlockingQueue,基於鍊表實現,所以可以把長度設為Integer.MAX_VALUE。利用鍊表的特徵,分離了takeLock與putLock兩把鎖,繼續用notEmpty、notFull管理隊列滿或空時的阻塞狀態。

◆補充

JDK7有個LinkedTransferQueue,transfer(e)方法保證Producer放入的元素,被Consumer取走了再返回,比SynchronousQueue更好,有空要學習下。

不斷更新中,請儘量訪問博客原文。

via:花錢的年華 的博客

地址:http://calvin1978.blogcn.com/articles/collection.html


一、IT技術領域:

回復"1",查看【Linux】「Linux學習資源推薦」;

回復"2",查看【Python】「Python語言十分鐘快速入門」;

回復"3",查看【Java】「寫好Java代碼的30條經驗總結」;

回復"4",查看【PHP】「你不得不知道的20個優秀PHP框架」;

回復"5",查看【WEB】「Web開發者不可不知的15條編碼原則」;

回復"6",查看【C】「C語言謎題」;

回復"7",查看【Android】「移動終端開發必備知識」;

二、程式設計師技能:

回復"算法",查看【算法】「各語言排序算法原始碼」;

回復"git",查看【git】「簡明 Git 命令速查表(中文版)」;

回復"sql",查看【資料庫】「Mysql中常用的sql語句匯總」;

三、程式設計師:

回復"kh",查看程式設計師的4大困惑

回復"gjh",查看那些高級黑程式設計師的段子:

相關焦點

  • java入門必備書籍
    本書深入介紹了Java編程的相關方面,全書內容覆蓋了Java的基本語法結構、Java的面向對象特徵、Java集合框架體系、Java泛型、異常處理、Java GUI編程、JDBC資料庫編程、Java注釋、Java的IO流體系、
  • java基礎教程:Collection集合,Collection 常用API
    集合概述在前面基礎班我們已經學習過並使用過集合ArrayList<E> ,那麼集合到底是什麼呢?集合:集合是java中提供的一種容器,可以用來存儲多個數據。集合和數組既然都是容器,它們有什麼區別呢?數組的長度是固定的。集合的長度是可變的。
  • Java初學者入門指南,值得收藏~
    很多Java編程初學者在剛接觸Java語言程序的時候,不知道該學習掌握哪些必要的基礎知識。小編總結了零基礎學習Java程式語言的幾個基礎知識要點。希望能夠對剛入門的Java新手有幫助。初學者先弄清這些Java的基本概念也是必不可少的,死記硬背肯定是不行的,重在理解,理解它們之間的區別與聯繫,分別有哪些應用。想想這些代碼中用到了哪些知識點。
  • java集合詳解合集
    一旦在數組初始化時指定了這個數組長度,這個數組長度就是不可變的,如果我們需要保存一個可以動態增長的數據(在編譯時無法確定具體的數量),java的集合類就是一個很好的設計方案了。集合類主要負責保存、盛裝其他數據,因此集合類也被稱為容器類。
  • 死磕 java所有集合之終結篇
    點擊下面連結可以直接到相應的章節查看:死磕 java集合之HashMap源碼分析死磕 java集合之LinkedHashMap源碼分析死磕 java集合之WeakHashMap源碼分析死磕 java集合之TreeMap源碼分析(一)死磕 java集合之TreeMap源碼分析(二)死磕 java集合之TreeMap
  • JAVA多線程 集合同步
    LinkedList@See http://sudotutorials.com/tutorials/java/collections/java-linkedlist-class.html6.原文連結:http://www.javamadesoeasy.com/2015/12/how-to-synchronize-arraylist-in-java-to.html幾乎所有的集合非線程安全的?
  • Java學習心得--給初學者的一些建議
    在筆者看來,學習一門語言必備的幾個要點在於,看,練,悟。在這個連技術也已經淪為快餐的時代,很多人無可厚非的認為,在短時間內,快速應用一門語言才是他們所追求的,這也造成了當今培訓機構的泛濫。我對此不評價,存在既是合理。
  • 給Java新手的一些建議——Java知識點歸納(Java基礎部分)
    Java的運行(基礎必備)這條可能出看很簡單,java程序的運行誰不會呢?不過很多時候, 我們只是單純通過IDE去執行java程序,底層IDE又是如何執行java程序呢?很多人並不了解。這個知識點是最最基本的java開發者需要掌握的,初學java,第一個肯定是教你如何在命令行中執行java程序,但是很多人一旦把java學完了,IDE用上了,就把這個都忘了。
  • java 基礎 之 集合 Map
    //: containers/Maps.java// Things you can do with Maps.import java.util.concurrent.*;import java.util.*;import net.mindview.util.
  • Java基礎學習心得筆記
    對於很多只會C語言的初學者而言,面對java基礎語法學習,反而感覺很難,其實其中最大的問題不是語法難,而是一種編程思想的轉變。
  • 程式設計師:java集合介紹,帶你深入理解list集合
    Java集合介紹作為一個程序猿,Java集合類可以說是我們在工作中運用最多、最頻繁的類。相比於數組(Array)來說,集合類的長度可變,更加方便開發。Java集合就像一個容器,可以存儲任何類型的數據,也可以結合泛型來存儲具體的類型對象。在程序運行時,Java集合可以動態的進行擴展,隨著元素的增加而擴大。
  • Java程式設計師進階:Java4大核心基礎必備知識點
    初學者先弄清這些Java的基本概念也是必不可少的,死記硬背肯定是不行的,重在理解,理解它們之間的區別與聯繫,分別有哪些應用。想想這些代碼中用到了哪些知識點,不要一味地照著書本敲代碼,而不去理解。要知道java是分兩部分的:一個是編譯,一個是運行。javac:負責的是編譯的部分,當執行javac時,會啟動java的編譯器程序。對指定擴展名的.java文件進行編譯,生成了jvm可以識別的字節碼文件,也就是class文件,也就是java的運行程序。
  • 50道Java集合經典面試題(收藏版)
    : [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; at Test.main(Test.java:14)Array 轉List使用Arrays.asList() 把數組轉換成集合時,不能使用修改集合相關的方法啦,如下:String
  • 如何學習Java,哪裡開始學Java比較好?
    2021-01-03 16:32:07 來源: IT培訓 舉報   java
  • java集合總結-複習
    Map第一張圖裡 List有序有重複,Set無序無重複,這些基本都書序java程式設計師必須知道的一件事情.二叉樹是n(n>=0)個結點的有限集合,該集合或者為空集(稱為空二叉樹),或者由一個根結點和兩棵互不相交的、分別稱為根結點的左子樹和右子樹組成。
  • java學習之高級語法——Set 集合
    Set 集合 java.util.Set 接口 extends Collection 接口Set 接口的特點:(1)不允許存儲重複的元素(2)沒有索引,沒有帶索引的方法,也不能使用普通的for循環遍歷
  • java 初學者 第一階段作業編程總結及心得體會
    0.前言 第一階段java作業分為
  • java基礎之一:認識程序和java
    程序的概念:指令集合程式語言:寫程序的工具java:程式語言中的一種,簡單好學,功能強大。。。如何開發java安裝jdk,通過記事本編寫,然後進入DOS控制臺,用javac編譯,java執行。。。通過程序可以得到源程序,這個過程叫反編譯,通過 jad,參考http://java-decompiler.github.io/java程序的基本結構:花括號包裹注意分號
  • java之ArrayList集合存儲基本數據類型
    各位小夥伴們大家好,這次小編要介紹的是,在ArrayList集合當中,基本數據類型的存儲,在之前的文章中小編有提到過,ArrayList集合只能存儲引用數據類型,其實也可以存儲基本數據類型。在ArrayList集合當中,如果要存儲基本數據類型需要用到基本數據類型相對應的包裝類(包裝類是引用數據類型,位於java.long包下,不需要導包)。小編先插入一個表格,來介紹基本數據類型與包裝類的對應關係。小編覺得,關於基本數據類型相對應的包裝類,其實大部分只需要首字母大寫就可以。int型還有char型比較特殊,這兩個需要大家簡單記一下。
  • java集合之LinkedList源碼分析
    一、查看類的繼承關係關於Cloneable,java.io.Serializable兩個接口和為什麼沒有實現RandomAccess接口,我們上次已經說過了,如果不是很理解,請參考上一篇文章:《java集合之ArrayList源碼分析》這次重點把LinkedList源碼看一遍先簡單說明:LinkedList 內部是一個鍊表LinkedList首先會繼承AbstractSequentialList,並實現List和Deque接口AbstractSequentialList類繼承了 AbstractList