原文地址java
Java集合框架:是一種工具類,就像是一個容器能夠存儲任意數量的具備共同屬性的對象。算法
Java集合中成員很豐富,經常使用的集合有ArrayList,HashMap,HashSet等。線程安全的有Vector,HashTable。線程不安全的有LinkedList,TreeMap,ArrayList,HashMap等等。編程
集合中用到的數據結構有如下幾種:數組
數組:最經常使用的數據結構之一。數組的特色是長度固定,能夠用下標索引,而且全部的元素的類型都是一致的。使用時儘可能把數組封裝在一個類裏,防止數據被錯誤的操做弄亂。緩存
鏈表:是一種由多個節點組成的數據結構,而且每一個節點包含有數據以及指向下一個節點的引用,在雙向鏈表裏,還會有一個指向前一個節點的引用。例如,能夠用單向鏈表和雙向鏈表來實現堆棧和隊列,由於鏈表的兩端都是能夠進行插入和刪除的動做的。固然,也會有在鏈表的中間頻繁插入和刪除節點的場景。安全
樹:是一種由節點組成的數據結構,每一個節點都包含數據元素,而且有一個或多個子節點,每一個子節點指向一個父節點能夠表示層級關係或者數據元素的順序關係。若是樹的每一個子節點最多有兩個葉子節點,那麼這種樹被稱爲二叉樹。二叉樹是一種很是經常使用的樹形結構, 由於它的這種結構使得節點的插入和刪除都很是高效。樹的邊表示從一個節點到另一個節點的快捷路徑。數據結構
堆棧:只容許對最後插入的元素進行操做(也就是後進先出,Last In First Out – LIFO)。若是你移除了棧頂的元素,那麼你能夠操做倒數第二個元素,依次類推。這種後進先出的方式是經過僅有的peek(),push()和pop()這幾個方法的強制性限制達到的。這種結構在不少場景下都很是實用,例如解析像(4+2)*3這樣的數學表達式,把源碼中的方法和異常按照他們出現的順序放到堆棧中,檢查你的代碼看看小括號和花括號是否是匹配的,等等。多線程
隊列:和堆棧有些類似,不一樣之處在於在隊列裏第一個插入的元素也是第一個被刪除的元素(便是先進先出)。這種先進先出的結構是經過只提供peek(),offer()和poll()這幾個方法來訪問數據進行限制來達到的。例如,排隊等待公交車,銀行或者超市裏的等待列隊等等,都是能夠用隊列來表示。併發
Java集合框架圖app
[圖片上傳失敗...(image-4b8b54-1530872801038)]
如上圖所示,Collection接口是最基本的集合接口,它不提供直接的實現,Java SDK提供的類都是繼承自Collection的「子接口」如List,Set和Queue。Collection所表明的是一種規則,它所包含的元素都必須遵循一條或者多條規則。若有些容許出現重複元素而有些則不容許重複、有些必需要按照順序插入而有些則是散列,有些支持排序可是有些則不支持等等。
List接口是Collection接口下的子接口。List所表明的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶能夠對列表中每一個元素的插入位置進行精確地控制,同時能夠根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
ArrayList基於數組實現,能夠經過下標索引直接查找到指定位置的元素,所以查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。它容許任何符合規則的元素插入甚至包括null。每個ArrayList都有一個初始容量(10),該容量表明瞭數組的大小。隨着容器中的元素不斷增長,容器的大小也會隨着增長。在每次向容器中增長元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操做(擴容1.5倍)。因此若是咱們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操做而浪費時間、效率。
ArrayList擅長於隨機訪問。同時ArrayList是非同步的,只能用在單線程環境下,多線程環境下能夠考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可使用concurrent併發包下的CopyOnWriteArrayList類。
擴充容量的方法ensureCapacity。ArrayList在每次增長元素(多是1個,也多是一組)時,都要調用該方法來確保足夠的容量。當容量不足以容納當前的元素個數時,就設置新的容量爲舊的容量的1.5倍,若是設置後的新容量還不夠,則直接新容量設置爲傳入的參數(也就是所需的容量),然後用Arrays.copyof()方法將元素拷貝到新的數組。從中能夠看出,當容量不夠時,每次增長元素,都要將原來的元素拷貝到一個新的數組中,很是之耗時,也所以建議在事先能肯定元素數量的狀況下,才使用ArrayList,不然建議使用LinkedList。
LinkedList一樣實現List接口,與ArrayList不一樣的是,LinkedList是基於雙向鏈表實現的,能夠在任何位置進行高效地插入和移除操做。可是LinkedList不能隨機訪問,它全部的操做都是要按照雙重鏈表的須要執行。在列表中索引的操做將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣作的好處就是能夠經過較低的代價在List中進行插入和刪除操做。
與ArrayList同樣,LinkedList也是非同步的。若是多個線程同時訪問一個List,則必須本身實現訪問同步。一種解決方法是在建立List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
與ArrayList類似,可是Vector是同步的。因此說Vector是線程安全的動態數組。它的操做與ArrayList幾乎同樣。
Stack繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被看成堆棧使用。基本的push和pop 方法,還有peek方法獲得棧頂的元素,empty方法測試堆棧是否爲空,search方法檢測一個元素在堆棧中的位置。Stack剛建立後是空棧。
Set接口繼承了Collection接口。Set集合中不能包含重複的元素,每一個元素必須是惟一的。你只需將元素加入set中,重複的元素會自動移除。有三種常見的Set實現——HashSet, TreeSet和LinkedHashSet。若是你須要一個訪問快速的Set,你應該使用HashSet;當你須要一個排序的Set,你應該使用TreeSet;當你須要記錄下插入時的順序時,你應該使用LinedHashSet。
HashSet是是基於 HashMap 實現的,底層採用 HashMap 來保存元素,因此它不保證set 的迭代順序;特別是它不保證該順序恆久不變。add()、remove()以及contains()等方法都是複雜度爲O(1)的方法。因爲HashMap中key不可重複,因此HashSet元素不可重複。能夠存儲null元素,是線程不安全的。
TreeSet是一個有序集,基於TreeMap實現,是線程不安全的。
TreeSet底層採用TreeMap存儲,構造器啓動時新建TreeMap。TreeSet存儲元素實際爲TreeMap存儲的鍵值對爲<key,PRESENT>的key;,PRESENT爲固定對象:private static final Object PRESENT = new Object().
TreeSet支持兩種兩種排序方式,經過不一樣構造器調用實現
天然排序:
public TreeSet() { // 新建TreeMap,天然排序 this(new TreeMap<E,Object>()); }
Comparator排序:
public TreeSet(Comparator<? super E> comparator) { // 新建TreeMap,傳入自定義比較器comparator this(new TreeMap<>(comparator)); }
TreeSet支持正向/反向迭代器遍歷和foreach遍歷
// 順序TreeSet:迭代器實現 Iterator iter = set.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } // 順序遍歷TreeSet:foreach實現 for (Integer i : set) { System.out.println(i); } // 逆序遍歷TreeSet:反向迭代器實現 Iterator iter1 = set.descendingIterator(); while (iter1.hasNext()) { System.out.println(iter1.next()); }
LinkedHashSet介於HashSet和TreeSet之間。哈希表和連接列表實現。基本方法的複雜度爲O(1)。
LinkedHashSet 是 Set 的一個具體實現,其維護着一個運行於全部條目的雙重連接列表。此連接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。
LinkedHashSet 繼承於 HashSet,而且其內部是經過 LinkedHashMap 來實現的。有點相似於咱們以前說的LinkedHashMap 其內部是基於 Hashmap 實現的同樣。
若是咱們須要迭代的順序爲插入順序或者訪問順序,那麼 LinkedHashSet 是須要你首先考慮的。
LinkedHashSet 底層使用 LinkedHashMap 來保存全部元素,由於繼承於 HashSet,全部的方法操做上又與 HashSet 相同,所以 LinkedHashSet 的實現上很是簡單,只提供了四個構造方法,並經過傳遞一個標識參數,調用父類的構造器,底層構造一個 LinkedHashMap 來實現,在相關操做上與父類 HashSet 的操做相同,直接調用父類 HashSet 的方法便可。
package java.util; public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -2851667679971038690L; /** * 構造一個帶有指定初始容量和加載因子的空鏈表哈希set。 * * 底層會調用父類的構造方法,構造一個有指定初始容量和加載因子的LinkedHashMap實例。 * @param initialCapacity 初始容量。 * @param loadFactor 加載因子。 */ public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } /** * 構造一個指定初始容量和默認加載因子0.75的新鏈表哈希set。 * * 底層會調用父類的構造方法,構造一個指定初始容量和默認加載因子0.75的LinkedHashMap實例。 * @param initialCapacity 初始容量。 */ public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } /** * 構造一個默認初始容量16和加載因子0.75的新鏈表哈希set。 * * 底層會調用父類的構造方法,構造一個默認初始容量16和加載因子0.75的LinkedHashMap實例。 */ public LinkedHashSet() { super(16, .75f, true); } /** * 構造一個與指定collection中的元素相同的新鏈表哈希set。 * * 底層會調用父類的構造方法,構造一個足以包含指定collection * 中全部元素的初始容量和加載因子爲0.75的LinkedHashMap實例。 * @param c 其中的元素將存放在此set中的collection。 */ public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED); } }
經過觀察HashMap的源碼咱們能夠發現:
Hash Map的前三個構造函數,即訪問權限爲public類型的構造函數均是以HashMap做爲實現。而以LinkedHashMap做爲實現的構造函數的訪問權限是默認訪問權限,即包內訪問權限。
即:在java編程中,經過new建立的HashSet對象均是以HashMap做爲實現基礎。只有在jdk中java.util包內的源代碼纔可能建立以LinkedHashMap做爲實現的HashSet(LinkedHashSet就是經過封裝一個以LinkedHashMap爲實現的HashSet來實現的)。
只有包含三個參數的構造函數纔是採用的LinkedHashMap做爲實現。
Map與List、Set接口不一樣,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關係。也就是說一個key對應一個value,因此它不能存在相同的key值,固然value值能夠相同。key能夠爲空,可是隻容許出現一個null。它的主要實現類有HashMap、HashTable、LinkedHashMap、TreeMap。
HashMap 是 Map 的一個實現類,它表明的是一種鍵值對的數據存儲形式。
大多數狀況下能夠直接定位到它的值,於是具備很快的訪問速度,但遍歷順序倒是不肯定的。
HashMap最多隻容許一條記錄的鍵爲null,容許多條記錄的值爲null。遇到key爲null的時候,調用putForNullKey方法進行處理,而對value沒有處理。不保證有序(好比插入的順序)、也不保證序不隨時間變化。
jdk 8 以前,其內部是由數組+鏈表來實現的,而 jdk 8 對於鏈表長度超過 8 的鏈表將轉儲爲紅黑樹。
HashMap非線程安全,即任一時刻能夠有多個線程同時寫HashMap,可能會致使數據的不一致。若是須要知足線程安全,能夠用 Collections的synchronizedMap方法使HashMap具備線程安全的能力,或者使用ConcurrentHashMap。
hash數組的默認大小是16,並且大小必定是2的指數
Hashtable和HashMap同樣也是散列表,存儲元素也是鍵值對,底層實現是一個Entry數組+鏈表。Hashtable繼承於Dictionary類(Dictionary類聲明瞭操做鍵值對的接口方法),實現Map接口(定義鍵值對接口)。HashTable是線程安全的,它的大部分類都被synchronized關鍵字修飾。key和value都不可爲null。
hash數組默認大小是11,擴充方式是old*2+1
LinkedHashMap繼承自HashMap實現了Map接口。基本實現同HashMap同樣(底層基於數組+鏈表+紅黑樹實現),不一樣之處在於LinkedHashMap保證了迭代的有序性。其內部維護了一個雙向鏈表,解決了 HashMap不能隨時保持遍歷順序和插入順序一致的問題。
除此以外,LinkedHashMap對訪問順序也提供了相關支持。在一些場景下,該特性頗有用,好比緩存。
在實現上,LinkedHashMap不少方法直接繼承自HashMap,僅爲維護雙向鏈表覆寫了部分方法。
默認狀況下,LinkedHashMap的迭代順序是按照插入節點的順序。也能夠經過改變accessOrder參數的值,使得其遍歷順序按照訪問順序輸出。
TreeMap繼承自AbstractMap抽象類,並實現了SortedMap接口,以下圖所示:
[圖片上傳失敗...(image-fd7a40-1530872801038)]
TreeMap集合是基於紅黑樹(Red-Black tree)的 NavigableMap實現。該集合最重要的特色就是可排序,該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
都是Java經常使用的容器,都是接口。不一樣的是List存儲的是單列的集合,Map存儲的是key-value鍵值對的集合。List中容許出現重複元素,Map中不容許key重複。List集合是有序的(儲存有序),Map集合是無序的(存儲無序)
Set大多都用的Map接口的實現類來實現的(HashSet基於HashMap實現,TreeSet基於TreeMap實現,LinkedHashSet基於LinkedHashMap實現)
在HashMap中經過以下實現來保證key值惟一
// 1. 若是key 相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 2. 修改對應的value if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; }
添加元素的時候,若是key(也對應的Set集合的元素)相等,那麼則修改value值。而在Set集合中,value值僅僅是一個Object對象罷了(該對象對Set自己而言是無用的)。
也就是說:Set集合若是添加的元素相同時,是根本沒有插入的(僅修改了一個無用的value值)。從源碼(HashMap)中也看出來,==和equals()方法都有使用!
相同點:
這兩個類都實現了List接口,他們都是有序的集合(儲存有序),底層都用數組實現。能夠經過索引來獲取某個元素。容許元素重複和出現null值。ArrayList和Vector的迭代器實現都是fail-fast的。
不一樣點:
vector是線程同步的,因此它也是線程安全的,而arraylist是線程異步的,是不安全的。若是不考慮到線程的安全因素,通常用arraylist效率比較高。
擴容時,arraylist擴容1.5倍,vector擴容2倍(或者擴容指定的大小)
ArrayList 和Vector是採用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,都容許直接序號索引元素,可是插入數據要設計到數組元素移動等內存操做,因此索引數據快插入數據慢,Vector因爲使用了synchronized方法(線程安全)因此性能上比ArrayList要差,LinkedList使用雙向鏈表實現存儲,按序號索引數據須要進行向前或向後遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入數度較快!
ArrayList是基於數組實現的,LinkedList基於雙向鏈表實現的。
ArrayList它支持如下標位置進行索引出對應的元素(隨機訪問),而LinkedList則須要遍歷整個鏈表來獲取對應的元素。所以通常來講ArrayList的訪問速度是要比LinkedList要快的
ArrayList因爲是數組,對於刪除和修改而言消耗是比較大(複製和移動數組實現),LinkedList是雙向鏈表刪除和修改只須要修改對應的指針便可,消耗是很小的。所以通常來講LinkedList的增刪速度是要比ArrayList要快的
LinkedList比ArrayList消耗更多的內存,由於LinkedList中的每一個節點存儲了先後節點的引用。
對於增長/刪除元素操做
若是增刪都是在末尾來操做(每次調用的都是remove()和add()),此時ArrayList就不須要移動和複製數組來進行操做了。若是數據量有百萬級的時,速度是會比LinkedList要快的。
若是刪除操做的位置是在中間。因爲LinkedList的消耗主要是在遍歷上,ArrayList的消耗主要是在移動和複製上(底層調用的是arraycopy()方法,是native方法)。
LinkedList的遍歷速度是要慢於ArrayList的複製移動速度的
若是數據量有百萬級的時,仍是ArrayList要快。
ArrayList、HashMap、TreeMap和HashTable類提供對元素的隨機訪問。
Enumeration的速度是Iterator的兩倍,也使用更少的內存。Enumeration是很是基礎的,也知足了基礎的須要。可是,與Enumeration相比,Iterator更加安全,由於當一個集合正在被遍歷的時候,它會阻止其它線程去修改集合。
Iterator的方法名比Enumeration更科學
Iterator有fail-fast機制,比Enumeration更安全
Iterator可以刪除元素,Enumeration並不能刪除元素
咱們可使用Iterator來遍歷Set和List集合,而ListIterator只能遍歷List。
Iterator只能夠向前遍歷,而LIstIterator能夠雙向遍歷。
ListIterator從Iterator接口繼承,而後添加了一些額外的功能,好比添加一個元素、替換一個元素、獲取前面或後面元素的索引位置。
須要同時重寫該類的hashCode()方法和它的equals()方法。
從源碼能夠得知,在插入元素的時候是先算出該對象的hashCode。若是hashcode相等話的。那麼代表該對象是存儲在同一個位置上的。
若是調用equals()方法,兩個key相同,則替換元素
若是調用equals()方法,兩個key不相同,則說明該hashCode僅僅是碰巧相同,此時是散列衝突,將新增的元素放在桶子上
重寫了equals()方法,就要重寫hashCode()的方法。由於equals()認定了這兩個對象相同,而同一個對象調用hashCode()方法時,是應該返回相同的值的!
HashSet 實現了 Set 接口,它不容許集合中有重複的值,當咱們提到 HashSet 時,第一件事情就是在將對象存儲在 HashSet 以前,要先確保對象重寫 equals()和 hashCode()方法,這樣才能比較對象的值是否相等,以確保set中沒有儲存相等的對象。若是咱們沒有重寫這兩個方法,將會使用這個方法的默認實現。
public boolean add(Object o)方法用來在 Set 中添加元素,當元素值重複時則會當即返回 false,若是成功添加的話會返回 true。
HashMap 實現了 Map 接口,Map 接口對鍵值對進行映射。Map 中不容許重複的鍵。Map 接口有兩個基本的實現,HashMap 和 TreeMap。TreeMap 保存了對象的排列次序,而 HashMap 則不能。HashMap 容許鍵和值爲 null。HashMap 是非 synchronized 的,但 collection 框架提供方法能保證 HashMap synchronized,這樣多個線程同時訪問 HashMap 時,能保證只有一個線程更改 Map。
public Object put(Object Key,Object value)方法用來將元素添加到 map 中。
HashMap | HashSet |
---|---|
HashMap實現了Map接口 | HashSet實現了Set接口 |
HashMap儲存鍵值對 | HashSet僅僅存儲對象 |
使用put()方法將元素放入map中 | 使用add()方法將元素放入set中 |
HashMap中使用鍵對象來計算hashcode值 | HashSet使用成員對象來計算hashcode值,對於兩個對象來講hashcode可能相同,因此equals()方法用來判斷對象的相等性,若是兩個對象不一樣的話,那麼返回false |
相同點:
儲存結構和實現基本相同,都是是實現的Map接口
不一樣點:
HashTable是同步的,HashMap是非同步的,須要同步的時候能夠ConcurrentHashMap方法
HashMap容許爲null,HashTable不容許爲null
繼承不一樣,HashMap繼承的是AbstractMap,HashTable繼承的是Dictionary
HashMap提供對key的Set進行遍歷,所以它是fail-fast的,但HashTable提供對key的Enumeration進行遍歷,它不支持fail-fast。
HashTable是一個遺留類,若是須要保證線程安全推薦使用CocurrentHashMap
HashMap經過hashcode對其內容進行快速查找,而TreeMap中全部的元素都保持着某種固定的順序,若是你須要獲得一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。HashMap中元素的排列順序是不固定的)。
在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。但若是您要按天然順序或自定義順序遍歷鍵,那麼TreeMap會更好。使用HashMap要求添加的鍵類明肯定義了hashCode()和 equals()的實現。 這個TreeMap沒有調優選項,由於該樹總處於平衡狀態。
Java1.5引入了泛型,全部的集合接口和實現都大量地使用它。泛型容許咱們爲集合提供一個能夠容納的對象類型,所以,若是你添加其它類型的任何元素,它會在編譯時報錯。這避免了在運行時出現ClassCastException,由於你將會在編譯時獲得報錯信息。泛型也使得代碼整潔,咱們不須要使用顯式轉換和instanceOf操做符。它也給運行時帶來好處,由於不會產生類型檢查的字節碼指令。
comparable接口其實是出自java.lang包
它有一個 compareTo(Object obj)方法來將objects排序
comparator接口其實是出自 java.util 包
它有一個compare(Object obj1, Object obj2)方法來將objects排序
Vector, Hashtable, Properties 和 Stack 都是同步的類,因此它們都線程安全的,能夠被使用在多線程環境中
使用Collections.synchronizedList(list)) 方法,能夠保證list類是線程安全的
使用java.util.Collections.synchronizedSet()方法能夠保證set類是線程安全的。
TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。
TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。
Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator接口的子類型(須要重寫compare方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。
Java PriorityQueue是一個數據結構,它是Java集合框架的一部分。 它是一個隊列的實現,其中元素的順序將根據每一個元素的優先級來決定。 實例化PriorityQueue時,能夠在構造函數中提供比較器。 該比較器將決定PriorityQueue集合實例中元素的排序順序。
equals()方法用於肯定兩個Java對象的相等性。 當咱們有一個自定義類時,咱們須要重寫equals()方法並提供一個實現,以便它能夠用來找到它的兩個實例之間的相等性。 經過Java規範,equals()和hashCode()之間有一個契約。 它說,「若是兩個對象相等,即obj1.equals(obj2)爲true,那麼obj1.hashCode()和obj2.hashCode()必須返回相同的整數」
不管什麼時候咱們選擇重寫equals(),咱們都必須重寫hashCode()方法。 hashCode()用於計算位置存儲區和key。