-
Java集合類主要由兩個接口派生而出:Collection和Map
從上面的集合框架圖能夠看到,Java集合框架主要包括兩種類型的容器,一種是集合(Collection),存儲一個元素集合,另外一種是圖(Map),存儲鍵/值對映射。Collection接口又有3種子類型,List、Set和Queue,再下面是一些抽象類,最後是具體實現類,經常使用的有ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap等等。 java
-
Collection接口是處理對象集合的根接口,其中定義了不少對元素進行操做的方法,AbstractCollection是提供Collection部分實現的抽象類。
-
List
List接口擴展自Collection,它能夠定義一個容許重複的有序集合,從List接口中的方法來看,List接口主要是增長了面向位置的操做,容許在指定位置上操做元素,同時增長了一個可以雙向遍歷線性表的新列表迭代器ListIterator。AbstractList類提供了List接口的部分實現,AbstractSequentialList擴展自AbstractList,主要是提供對鏈表的支持。下面介紹List接口的兩個重要的具體實現類,也是咱們可能最經常使用的類,ArrayList和LinkedList。 node
-
ArrayList
-
ArrayList是List接口的可變數組的實現。底層使用數組保存全部元素。其操做基本上是
對數組的操做 數組
-
實現了全部可選列表操做,並容許包括 null 在內的全部元素。
-
除了實現 List接口外,此類還提供一些方法來操做內部用來存儲列表的數組的大小。
-
每一個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。
它老是至少等於列表的大小。 安全
-
隨着向ArrayList中不斷添加元素,其容量也自動增加。自動增加會帶來數據向新數組的
從新拷貝,所以,若是可預知數據量的多少,可在構造ArrayList時指定其容量。 數據結構
注意: 多線程
-
當增長數據的時候,若是ArrayList的大小已經不知足需求時,那麼就將數組變爲原長度的1.5倍,以後的操做就是把老的數組拷到新的數組裏面。
-
LinkList
List 接口的連接列表實現。實現全部可選的列表操做,而且容許全部元素(包括 null)。除了實現 List 接口外,LinkedList 類還爲在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操做容許將連接列表用做堆棧、隊列或雙端隊列。 併發
此類實現 Deque 接口,爲 add、poll 提供先進先出隊列操做,以及其餘堆棧和雙端隊列操做。 框架
LinkList的實現原理總結 函數
-
數據存儲是基於雙向鏈表實現的。
-
②插入數據很快。先是在雙向鏈表中找到要插入節點的位置index,找到以後,再插入一個新節點。 雙向鏈表查找index位置的節點時,有一個加速動做:若index < 雙向鏈表長度的1/2,則從前向後查找; 不然,從後向前查找。
③刪除數據很快。先是在雙向鏈表中找到要插入節點的位置index,找到以後,進行以下操做:node.previous.next = node.next;node.next.previous = node.previous;node = null 。查找節點過程和插入同樣。 性能
-
獲取數據很慢,須要從Head節點進行查找。
-
-
⑤遍歷數據很慢,每次獲取數據都須要從頭開始。
注:
-
以雙向鏈表實現。鏈表無容量限制,但雙向鏈表自己使用了更多空間,也須要額外的鏈表指針操做。
-
-
Set
Set接口擴展自Collection,它與List的不一樣之處在於,規定Set的實例不包含重複的元素。在一個規則集內,必定不存在兩個相等的元素。AbstractSet是一個實現Set接口的抽象類,Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。
-
HashSet
HashSet實現Set接口,由哈希表(其實是一個HashMap實例)支持。它不保證set 的迭代順序;特別是它不保證該順序恆久不變。此類容許使用null元素。HashSet中不容許有重複元素,這是由於HashSet是基於HashMap實現的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是統一的一個private static final Object PRESENT = new Object();。HashSet跟HashMap同樣,都是一個存放鏈表的數組。
HashSet是基於HashMap來實現的,操做很簡單,更像是對HashMap作了一次"封裝",並且只使用了HashMap的key來實現各類特性
HashSet的實現原理總結以下:
①是基於HashMap實現的,默認構造函數是構建一個初始容量爲16,負載因子爲0.75 的HashMap。封裝了一個 HashMap 對象來存儲全部的集合元素,全部放入 HashSet 中的集合元素實際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個 PRESENT,它是一個靜態的 Object 對象。
②當咱們試圖把某個類的對象當成 HashMap的 key,或試圖將這個類的對象放入 HashSet 中保存時,重寫該類的equals(Object obj)方法和 hashCode() 方法很重要,並且這兩個方法的返回值必須保持一致:當該類的兩個的 hashCode() 返回值相同時,它們經過 equals() 方法比較也應該返回 true。一般來講,全部參與計算 hashCode() 返回值的關鍵屬性,都應該用於做爲 equals() 比較的標準。
③HashSet的其餘操做都是基於HashMap的。
-
LinkedHashSet
LinkedHashSet經過繼承HashSet,底層使用LinkedHashMap,以很簡單明瞭的方式來實現了其自身的全部功能。
-
TreeSet
TreeSet 是一個有序的集合,它的做用是提供有序的Set集合。它繼承於AbstractSet抽象類,實現了NavigableSet<E>, Cloneable, java.io.Serializable接口。
*TreeSet 繼承於AbstractSet,因此它是一個Set集合,具備Set的屬性和方法。
*TreeSet 實現了NavigableSet接口,意味着它支持一系列的導航方法。好比查找與指定目標最匹配項。
*TreeSet 實現了Cloneable接口,意味着它能被克隆。
*TreeSet 實現了java.io.Serializable接口,意味着它支持序列化。
*reeSet是基於TreeMap實現的。TreeSet中的元素支持2種排序方式:天然排序 或者 根據建立TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。
*reeSet爲基本操做(add、remove 和 contains)提供受保證的 log(n) 時間開銷。
* reeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
-
Queue
隊列是一種先進先出的數據結構,元素在隊列末尾添加,在隊列頭部刪除。Queue接口擴展自Collection,並提供插入、提取、檢驗等操做。
poll()與remove()方法都是移除隊列頭部的元素,二者的區別在於若是隊列爲空,那麼poll()返回的是null,而remove()會拋出一個異常。方法element()與peek()主要是獲取頭部元素,不刪除。
接口Deque,是一個擴展自Queue的雙端隊列,它支持在兩端插入和刪除元素,由於LinkedList類實現了Deque接口,因此一般咱們可使用LinkedList來建立一個隊列。PriorityQueue類實現了一個優先隊列,優先隊列中元素被賦予優先級,擁有高優先級的先被刪除。實際上有多個Queue的實現,有的是採用線性表實現,有的基於鏈表實現。還有的適用於多線程的環境。Java中具備Queue功能的類主要有以下幾個:AbstractQueue, ArrayBlockingQueue, ConcurrentLinkedQueue, LinkedBlockingQueue, DelayQueue, LinkedList, PriorityBlockingQueue, PriorityQueue和ArrayDqueue。
-
Map
是一種存儲鍵值對映射的容器類,在Map中鍵能夠是任意類型的對象,但不能有重複的鍵,每一個鍵都對應一個值,真正存儲在圖中的是鍵值構成的條目。從上面這張圖中咱們能夠看到接口Map提供了不少查詢、更新和獲取存儲的鍵值對的方法,更新包括方法clear()、put()、putAll()、remove()等等,查詢方法包括containsKey、containsValue等等。Map接口經常使用的有三個具體實現類,分別是HashMap、LinkedHashMap、TreeMap。
在實際使用中,若是更新圖時不須要保持圖中元素的順序,就使用HashMap,若是須要保持圖中元素的插入順序或者訪問順序,就使用LinkedHashMap,若是須要使圖按照鍵值排序,就使用TreeMap。
-
HashMap
HashMap實現了Map接口,繼承子AbstractMap。其中,Map接口定義了鍵映射到值的
規則。Java中HashMap是由數組和引用實現的"鏈表散列"。HashMap底層實現是數組,可是數組的每一項都是一個鏈表,其中initialCapacity就表明了數組的長度。Entry爲HashMap的內部類,它包含了鍵key、值value、下一個節點next,以及hash值。這個內部類很是重要,正是因爲Entry才構成table數組的項爲鏈表。
初始化HashMap時,系統會建立一個長度爲capacity的Entry數組,這個數組裏能夠存儲元素的位置被稱爲"桶(bucket)" 每一個 bucket 都有其指定索引,系統能夠根據其索引快速訪問該 bucket 裏存儲的元素。不管什麼時候,HashMap 的每一個"桶"只存儲一個元素(也就是一個 Entry),因爲 Entry 對象能夠包含一個引用變量(就是 Entry 構造器的的最後一個參數)用於指向下一個 Entry,所以可能出現的狀況是:HashMap 的 bucket 中只有一個 Entry,但這個 Entry 指向另外一個 Entry ——這就造成了一個 Entry 鏈。
數組中存儲的是一個鏈表的頭結點
threshold: 初始容量,表示哈希表中桶的數量。
loadFactor:負載因子,表示當前哈希表的最大填滿比例。當threshold * loadFactor < 當前哈希表中桶數目時,哈希表的threshold須要擴大爲當前的2倍。
-
HashMap的使用場景:當須要存儲鍵值對時須要使用HashMap,它能夠接收key爲null
的鍵值對,可是是非線程同步的。
-
HashMap的工做原理:
HashMap底層是數組實現的,數組的每一個元素是鏈表,由Entry內部類實現。HashMap經過put方法存儲對象,經過get方法獲取對象。
put存儲對象時,咱們將K/V鍵值對傳給put方法,它首先調用hash方法計算K的hash值,取餘HashMap數組長度後獲取該鍵值對所在鏈表的數組下標,進一步存儲時,會適當調整數組大小,而且採用頭插法將Entry鍵值對插入到鏈表中。
步驟:1)對key的hashCode()作hash,而後再計算index;
2)若是沒碰撞直接放到bucket裏;
3)若是碰撞了,以鏈表的形式存在buckets後;
4)若是碰撞致使鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹;
5)若是節點已經存在就替換old value(保證key的惟一性)
6)若是bucket滿了(超過load factor*current capacity),就要resize。
獲取對象時,咱們將K傳給get方法,也是先調用hash方法計算hash值獲取數組中所在鏈表的下標。而後,順序遍歷鏈表,查找相同Entry的key的value值。
-
LinkedHashMap
LinkedHashMap繼承自HashMap,它主要是用鏈表實現來擴展HashMap類,HashMap中條目是沒有順序的,可是LinkedHashMap經過維護一個運行於全部條目的雙向鏈表,LinkedHashMap保證了元素迭代的順序,LinkedHashMap中元素既能夠按照它們插入map的順序排序,也能夠按它們最後一次被訪問的順序排序。 對於LinkedHashMap而言,它繼承與HashMap、底層使用哈希表與雙向鏈表來保存全部元素。其基本操做與父類HashMap類似,它經過重寫父類相關的方法,來實現本身的連接列表特性。
關 注 點 |
結 論 |
LinkedHashMap是否容許空 |
Key和Value都容許空 |
LinkedHashMap是否容許重複數據 |
Key重複會覆蓋、Value容許重複 |
LinkedHashMap是否有序 |
有序(指插入順序等) |
LinkedHashMap是否線程安全 |
非線程安全 |
注:
-
LinkedHashMap能夠認爲是HashMap+LinkedList,即它既使用HashMap操做數據結構,又使用LinkedList維護插入元素的前後順序。
-
TreeMap
TreeMap基於紅黑樹數據結構的實現,鍵值可使用Comparable或Comparator接口來排序。TreeMap繼承自AbstractMap,同時實現了接口NavigableMap,而接口NavigableMap則繼承自SortedMap。SortedMap是Map的子接口,使用它能夠確保圖中的條目是排好序的。
映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。TreeMap的基本操做containsKey、get、put、remove方法,它的時間複雜度是log(n)
TreeMap本質是Red-Black Tree,它包含幾個重要的成員變量:root、size、comparator。其中root是紅黑樹的根節點。它是Entry類型,Entry是紅黑樹的節點,它包含了紅黑樹的6個基本組成:key、value、left、right、parent和color。Entry節點根據根據Key排序,包含的內容是value。Entry中key比較大小是根據比較器comparator來進行判斷的。size是紅黑樹的節點個數。
-
其餘集合類
-
Vector
Vector 是矢量隊列。和ArrayList不一樣,Vector中的操做是線程安全的。由於Vector底層是使用數組實現的,因此它的操做都是對數組進行操做,只不過其是能夠隨着元素的增長而動態的改變容量大小,其實現方法是是使用Arrays.copyOf方法將舊數據拷貝到一個新的大容量數組中。
1) vector能夠實現可增加的對象數組。與數組同樣,它包含可使用整數索引進行訪問的組件。不過,Vector的大小是能夠增長或者減少的,以便適應建立Vector後進行添加或者刪除操做。
2)Vector實現List接口,繼承AbstractList類,因此咱們能夠將其看作隊列,支持相關的添加、刪除、修改、遍歷等功能。
3)Vector實現RandmoAccess接口,即提供了隨機訪問功能,提供提供快速訪問功能。在Vector咱們能夠直接訪問元素。
4)Vector 實現了Cloneable接口,支持clone()方法,能夠被克隆。
-
ConcurrentHashMap
前提:HashMap中未進行同步考慮,而Hashtable則使用了synchronized,帶來的直接影響就是可選擇,咱們能夠在單線程時使用HashMap提升效率,而多線程時用Hashtable來保證安全。經過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,安全的背後是巨大的浪費。
左邊即是Hashtable的實現方式---鎖整個hash表;而右邊則是ConcurrentHashMap的實現方式---鎖桶(或段)。ConcurrentHashMap將hash表分爲16個桶(默認值),諸如get,put,remove等經常使用操做只鎖當前須要用到的桶。
ConcurrentHashMap只有在求size等操做時才須要鎖定整個表。而在迭代時,ConcurrentHashMap使用了不一樣於傳統集合的快速失敗迭代器的另外一種迭代方式,咱們稱爲弱一致迭代器。在這種迭代方式中,當iterator被建立後集合再發生改變就再也不是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據,iterator完成後再將頭指針替換爲新的數據,這樣iterator線程可使用原來老的數據,而寫線程也能夠併發的完成改變,更重要的,這保證了多個線程併發執行的連續性和擴展性,是性能提高的關鍵。
ConcurrentHashMap中主要實體類就是三個:
1) ConcurrentHashMap(整個Hash表);
2) Segment(桶);
3) HashEntry(節點)
ConcurrentHashMap容許多個修改操做併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不一樣部分進行的修改。ConcurrentHashMap內部使用段(Segment)來表示這些不一樣的部分,每一個段其實就是一個小的hash table,它們有本身的鎖。只要多個修改操做發生在不一樣的段上,它們就能夠併發進行。
-
CopyOnWriteArrayList
CopyOnWriteArrayList,是一個線程安全的List接口的實現,它使用了ReentrantLock鎖來保證在併發狀況下提供高性能的併發讀取。
Copy-On-Write簡稱COW,是一種用於程序設計中的優化策略。其基本思路是,從一開始你們都在共享同一個內容,當某我的想要修改這個內容的時候,纔會真正把內容Copy出去造成一個新的內容而後再改,這是一種延時懶惰策略。
CopyOnWrite容器即寫時複製的容器。通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。
CopyOnWriteArrayList中add方法的實現(向CopyOnWriteArrayList裏添加元素),能夠發如今添加的時候是須要加鎖的,不然多線程寫的時候會Copy出N個副本出來。
讀的時候不須要加鎖,若是讀的時候有多個線程正在向CopyOnWriteArrayList添加數據,讀仍是會讀到舊的數據,由於寫的時候不會鎖住舊的CopyOnWriteArrayList。
缺點:
-
內存佔用問題。由於CopyOnWrite的寫時複製機制,因此在進行寫操做的時候,內存裏會同時駐紮兩個對象的內存,舊的對象和新寫入的對象(注意:在複製的時候只是複製容器裏的引用,只是在寫的時候會建立新對象添加到新容器裏,而舊容器的對象還在使用,因此有兩份對象內存)。
-
數據一致性問題。CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。因此若是你但願寫入的的數據,立刻能讀到,請不要使用CopyOnWrite容器。