標簽(空格分隔): Java集合框架
問題思考
- 什么是集合框架?
- 為什么用集合框架?
- 怎么用集合框架?
問題解決
什么是集合框架
1. 什么是Collection
Collection(有時候也叫container)是一個簡單的對象,它把多個元素組織成一個單元。集合可以用來存儲、檢索、操作、通信。通常情況下,集合代表了一個自然數據項,比如一組手牌(牌的集合)、郵件文件夾(郵件的集合)、電話目錄(姓名到電話的映射)。如果你使用過Java或者其他語言,你應該很熟悉集合。
JDK1.8官方文檔
集合架構層次中的根接口。 集合表示一組被稱為其元素的對象。 一些集合允許重復元素,而有些則不允許。 有些有序,有些無序。 JDK不提供任何這種接口的直接實現:它提供了更多特定的子接口(如Set和List)的實現。 該接口通常用于傳遞集合,并在需要最大的通用性的情況下對其進行操作。
2. 什么是Collections Framework
Collections Framework是一個用來表示和操作集合的統一的架構。集合的框架包括了:
-
Interfaces:
這些是表示集合的抽象數據類型,接口允許集合完成操作,獨立與其詳細的實現。在面向對象的語言中,接口構成了體系架構; -
Implementations:
這些是接口的具體實現。本質上,是一些可復用的數據結構; -
Algorithms:
這些方法可以對接口實現的對象進行有用的計算,比如搜索、排序。這些算法是具有多態性的:也就是說,同樣的方法可以用在合適的接口的不同實現。本質上,是一些可復用的函數。
3. 集合框架體系圖
-
集合框架精簡圖
java-coll.png-77.4kB -
集合框架完整圖
573349f70001988006430611.gif-24.2kB -
集合框架解構圖-Collection
TB21HYoeVXXXXaLXXXXXXXXXXXX_!!581166664.jpeg-46kB -
集合框架解構圖-Map
TB2JzW7eVXXXXbRXpXXXXXXXXXX_!!581166664.jpg-23.6kB -
集合框架通用實現類
屏幕快照 2017-03-27 下午12.35.56.png-26.7kB
為什么用集合框架
減少編程的工作量:通過提供有用的數據結構和算法,集合框架能讓你更專注的實現程序的核心功能,而不是去做一個底層的“管道工”。Java框架通過促進無關API的互操作性,使得你不用自己去實現不同API的適配
提高程序的速度與質量:集合框架提供了一些有用數據結構和算法的高性能、高質量的實現。每個接口的不同的實現也是可以互換的,所以程序可以通過切換集合來做一些調整。正因為你從實現數據結構的那些苦差事中脫離出來,你才可以有更多的實現去改善你自己程序的性能和質量
允許無關APIs的互操作:集合接口是API之間傳遞集合的一個“方言”,比如我的網絡管理API有一個節點名的集合,而GUI工具需要一個列標題的集合,即使是分開實現它們,我們的APIs也可以無縫的接合。
省力地學習和使用新API:
這是另一個領先的優勢,設計者和實現者沒必要在每次都重新設計API的時候都“推倒重來”地實現集合,而是直接使用標準的集合接口就好了。促進軟件的復用:符合標準集合接口的新數據結構本質上是可以復用的。對于操作這些新數據結構算法也是一樣可以復用的。
怎么用集合框架
Map接口
1. HashMap
JDK1.8官方文檔
基于哈希表的Map接口實現。此實現提供了所有可選的map操作,并允許null值和null鍵。(HashMap類大致相當于Hashtable,除了它是非同步的,并允許null)。這個類不保證map的順序;特別是它不能保證在一段時間內順序保持不變。
假設散列函數把元素在桶中均勻分布,這種實現為基本操作(get和put)提供了恒定時間的性能。集合視圖的迭代需要與HashMap實例的“容量”(桶數)加上其大小(鍵值映射數)成比例的時間。因此,如果迭代性能很重要,不要將初始容量設置得太高(或負載因子太低)。
HashMap的一個實例有兩個參數影響其性能:初始容量和負載因子。容量是哈希表中的桶數,初始容量只是創建哈希表時的容量。負載因子是在容量自動增加之前測量哈希表容量有多大的百分比。當哈希表中的元素數量超過負載因子和當前容量的乘積時,重新排列哈希表(即,內部數據結構被重新構建),以使散列表具有大約兩倍原容量的桶數。
作為一般規則,默認負載因子(0.75)提供了時間和空間成本之間的均衡。較高的值會降低空間開銷,但會增加查找成本(反映在HashMap類的大部分操作中,包括get和put)。在設置其初始容量時,應考慮map中預期容量及其負載因子,以便最小化rehash操作的次數。如果初始容量大于最大容量數除以負載因子,則不會發生重新排列操作。
如果要將許多映射存儲在HashMap實例中,則以足夠大的容量創建映射將允許映射的存儲效率高于使其按需要執行自動重新排序來增長表。請注意,使用具有相同hashCode()的許多密鑰是降低任何哈希表的性能的一種可靠的方法。為了改善影響,當鍵是可比較的時候,這個類可以使用鍵之間的比較順序來幫助打破約束關系。
請注意,此實現不同步。如果多個線程同時訪問哈希映射,并且至少有一個線程在結構上修改了映射,那么它必須在外部進行同步。 (結構修改是添加或刪除一個或多個映射的任何操作;僅改變與實例已經包含的密鑰相關聯的值不是結構性修改。)這通常通過對自然地封裝映射的一些對象進行同步來實現。如果沒有這樣的對象存在,應該使用Collections.synchronizedMap方法“包裝”map。這最好在創建時完成,以防止意外的不同步訪問map:
Map m = Collections.synchronizedMap(new HashMap(...));
所有這個類的“集合視圖方法”返回的迭代器都是故障快速的:如果映射在迭代器創建之后的任何時間被結構化地修改,除了通過迭代器自己的remove方法之外,迭代器將拋出一個ConcurrentModificationException 。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來未確定的時間冒著任意的非確定性行為。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速的迭代器盡力地拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。
-
官方文檔重點
- 不保證有序(比如插入的順序),也不保證序不隨時間變化
- 允許null值和null鍵
- 線程不安全
存儲結構整體
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
int threshold;
final float loadFactor;
}
- 存儲結構Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
- 存儲結構TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
}
2. LinkedHashMap
JDK1.8文檔
哈希表和鏈接列表實現的Map接口,具有可預測的迭代順序。該實現與HashMap不同之處在于它保持了雙向鏈接列表。此鏈接列表定義迭代排序,通常是將鍵插入到map(插入順序)中的順序。請注意,如果將鍵重新插入到map中,則插入順序不受影響。(如果m.containsKey(k)在調用之前立即返回true,則調用m.put(k,v)時,將key k重新插入到映射m中。
該實現將客戶端從HashMap(和Hashtable)提供的不具體的、通常是混亂的排序中區分開,而不會導致與TreeMap相關聯的成本增加。無論原始map的實現如何,它都可用于生成與原始map相同順序的map副本:
void foo(Map m){
Map copy = new LinkedHashMap(m);
...
}
如果模塊在輸入上進行映射,復制它,然后返回由該副本決定其順序的結果,則此技術特別有用。 (客戶一般都喜歡以相同的順序返回)提供了一個特殊的構造函數來創建一個鏈接的哈希映射,它的迭代順序是最后訪問條目的順序,從最近訪問到最多訪問的(訪問順序)。這種map非常適合建立LRU緩存。調用put,putIfAbsent,get,getOrDefault,compute,computeIfAbsent,computeIfPresent或merge方法會導致對相應條目的訪問(假設調用完成后存在)。替換方法只有在替換值時才導致條目的訪問。 putAll方法按照指定map的條目集迭代器提供的鍵值映射的順序為指定map中的每個映射生成一個條目訪問。沒有其他方法生成條目訪問。特別地,對于集合視圖的操作不會影響后續map的迭代順序。
當將新的映射添加到地圖時,removeEldestEntry(Map.Entry)方法可能會被覆蓋,以強制執行自動刪除過時映射的策略。
此類提供了所有可選的Map操作,并允許null元素。像HashMap一樣,它為基本操作(add,contains和remove)提供了恒定的性能,假設散列函數在桶中將元素正確分散。由于維護鏈接列表的額外費用,性能可能略低于HashMap,除了一個例外:對LinkedHashMap的集合視圖進行迭代需要與map的大小成比例的時間,無論其容量如何。
HashMap上的迭代可能更昂貴,需要與其容量成比例的時間。
鏈接的哈希映射有兩個參數影響其性能:初始容量和負載因子。它們的定義與HashMap相同。但是,請注意,對于初始容量來說,選擇過高的值的后果沒有HashMap嚴重,因為此類的迭代次數不受容量的影響。
請注意,此實現不同步。如果多個線程同時訪問鏈接的散列映射,并且至少一個線程在結構上修改映射,則必須在外部進行同步。這通常通過在自然地封裝map的一些對象上同步來實現。如果沒有這樣的對象存在,應該使用Collections.synchronizedMap方法“包裝”map。這最好在創建時完成,以防止意外的不同步訪問map:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
結構修改是添加或刪除一個或多個映射的任何操作,或者在訪問有序鏈接的散列圖的情況下,影響迭代順序。在插入有序的鏈接散列圖中,僅改變與已經包含在map中的鍵相關聯的值不是結構修改。在訪問有序的鏈接散列圖中,只需用get查詢map就是結構修改。 )
由所有這個類的集合視圖方法返回的集合的迭代器方法返回的迭代器是fail-fast:如果映射在迭代器創建之后的任何時間被結構化地修改,除了通過迭代器自己的remove方法之外,迭代器將拋出一個ConcurrentModificationException異常。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來不確定的時間冒著任意的不確定性行為。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速迭代器拋出ConcurrentModifiionException盡力而為。 因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。由所有此類的所有返回的集合方法返回的Spliterator方法 集合視圖方法是晚期綁定,故障快速,另外報告Spliterator.ORDERED。
-
官方文檔重點
- 雙向鏈表
- 默認按插入順序排序,即順序確定
- 非同步,快速失敗
-
結構圖整體
20161116224219368.jpg-134.9kB -
結構圖循環鏈表
20161116224354806.jpg-118.9kB -
大神總結
3. TreeMap
JDK1.8文檔
基于Red-Black樹的NavigableMap實現。該map根據其鍵的自然排序,或者由map創建時提供的比較器進行排序,這取決于使用哪個構造函數。此實現為containsKey,get,put和remove操作提供了保證的log(n)時間成本。算法是Cormen,Leiserson和Rivest的算法介紹中的算法的適應性。
請注意,如果此排序的map要正確實現Map接口,則由TreeMap維護的排序(如任何排序映射)以及是否提供顯式比較器必須與equals一致。 (參見可比較或比較器,以獲得與equals一致的精確定義)這是因為Map接口是根據equals操作定義的,但是排序的map使用compareTo(或比較)方法來執行所有的密鑰比較,所以兩個從排序map的角度來說,通過該方法認為相等的鍵是相等的。排序map的行為是明確定義的,即使其排序與equals不一致;它只是不遵守map接口的通常規定。
請注意,此實現不同步。如果多個線程同時訪問map,并且至少一個線程在結構上修改映射,則必須在外部進行同步。 (結構修改是添加或刪除一個或多個map的任何操作;僅改變與現有密鑰相關聯的值不是結構修改。)這通常通過對自然封裝map的一些對象進行同步來實現。如果沒有這樣的對象存在,應該使用Collections.synchronizedSortedMap方法“包裝”map。這最好在創建時完成,以防止意外的不同步訪問map:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
由所有這個類的“集合視圖方法”返回的集合的迭代器方法返回的迭代器是故障快速的:如果映射在迭代器創建之后的任何時間被結構化地修改,除了通過迭代器自己的remove方法,迭代器會拋出一個ConcurrentModificationException。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來未確定的時間冒著任意的非確定性行為。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速的迭代器盡力地拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。
由此類中的方法返回的所有Map.Entry對及其視圖都代表生成map時的快照。他們不支持Entry.setValue方法。(請注意,可以使用put更改關聯地圖中的映射。)
- 官方文檔重點
- 紅黑樹算法實現
- 恒定的log(n)時間復雜度
- 默認按鍵的自然順序排序,但可提供比較器
- 非同步,快速失敗
4. HashTable
JDK1.8官方文檔
該類實現了一個哈希表,它將鍵映射到值。任何非空對象都可以用作鍵或值。要從哈希表成功存儲和檢索對象,用作鍵的對象必須實現hashCode方法和equals方法。
Hashtable的一個實例有兩個影響其性能的參數:初始容量和負載因子。容量是哈希表中的桶數,初始容量只是創建哈希表時的容量。請注意,哈希表是打開的:在“哈希沖突”的情況下,單個存儲桶存儲多個條目,必須依次搜索。負載因子是在容量自動增加之前允許哈希表得到滿足的度量。初始容量和負載因子參數僅僅是實現的提示。關于何時以及是否調用rehash方法的具體細節是依賴于實現的。
通常,默認負載因子(.75)提供了時間和空間成本之間的良好折衷。更高的值會減少空間開銷,但會增加查詢條目的時間(這反映在大多數Hashtable操作中,包括get和put)。
初始容量控制了浪費空間與需要重新運行操作之間的折中,這是耗時的。如果初始容量大于Hashtable將包含的最大條目數除以其負載因子,則不會發生重新排列操作。然而,設置初始容量太高可能會浪費空間。
如果將許多條目設置為Hashtable,則以足夠大的容量創建它可能會使條目更有效地插入,使其根據需要執行自動重新排序以擴展表。
此示例創建數字的散列表。它使用數字的名稱作為鍵:
Hashtable <String,Integer>數字
= new Hashtable <String,Integer>();
number.put(“one”,1);
numbers.put(“two”,2);
numbers.put(“three”,3);
要檢索一個數字,請使用以下代碼:
整數n = numbers.get(“two”);
if(n!= null){
System.out.println(“two =”+ n);
}
由所有這個類的“集合視圖方法”返回的集合的迭代器方法返回的迭代器是fail-fast:如果Hashtable在迭代器創建之后的任何時間被結構地修改,除了通過迭代器自己的remove方法,迭代器會拋出一個ConcurrentModificationException。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來未確定的時間冒著任意的非確定性行為。 Hashtable的鍵和元素方法返回的枚舉不是故障快速的。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速的迭代器盡力地拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。
從Java 2平臺v1.2開始,該類被改造成實現Map接口,使其成為Java Collections Framework的成員。與新的集合實現不同,Hashtable是同步的。如果不需要線程安全的實現,建議使用HashMap代替Hashtable。如果需要線程安全的高并發實現,那么建議使用ConcurrentHashMap代替Hashtable。
- 官方文檔重點
- 遺留類
- 不再用
Collection接口
1. HashSet
JDK1.8官方文檔
此類實現了Set接口,由哈希表(實際上是HashMap實例)支持。對集合的迭代次序不作任何保證;特別是,它不能保證訂單在一段時間內保持不變。此類允許null元素。
假定散列函數正確地分散在這些存儲區中,這個類可以為基本操作(add,remove,contains和size)提供恒定的時間性能。迭代此集合需要與HashSet實例的大小(元素數量)加上后備HashMap實例的“容量”(桶數)的總和成比例。因此,如果迭代性能很重要,不要將初始容量設置得太高(或負載因子太低)是非常重要的。
請注意,此實現不同步。如果多個線程并發訪問哈希集,并且至少有一個線程修改該集合,那么它必須在外部進行同步。這通常通過在自然地封裝集合的一些對象上進行同步來實現。如果沒有這樣的對象存在,則該集合應該使用Collections.synchronizedSet方法“包裝”。這最好在創建時完成,以防止對該集合的意外不同步訪問:
設置s = Collections.synchronizedSet(new HashSet(...));
這個類的迭代器方法返回的迭代器是故障快速的:如果在迭代器創建之后的任何時候修改了該集合,除了通過迭代器自己的remove方法之外,迭代器會拋出一個ConcurrentModificationException異常。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來未確定的時間冒著任意的非確定性行為。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速的迭代器盡力地拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。
2. LinkedHashSet
3. TreeSet
4. ArrayList
5. LinkedList
6. Vector
JDK1.8官方文檔
Vector類實現可擴展的對象數組。像數組一樣,它包含可以使用整數索引訪問的組件。但是,Vector的大小可以根據需要增長或縮小,以適應在向量創建后添加和刪除項目。
每個向量嘗試通過維護容量和容量來優化存儲管理。容量總是至少與矢量大小一樣大;它通常較大,因為將組件添加到向量中,向量的存儲以塊大小增加capacityIncrement的大小。應用程序可以在插入大量組件之前增加向量的容量;這減少了增量重新分配的數量。
這個類的迭代器和listIterator方法返回的迭代器是故障快速的:如果向量在迭代器創建之后的任何時間被結構地修改,除了通過迭代器自己的remove或add方法之外,迭代器將拋出ConcurrentModificationException。因此,面對并發修改,迭代器將快速而干凈地失敗,而不是在未來未確定的時間冒著任意的非確定性行為。元素方法返回的枚舉不是故障快速的。
請注意,迭代器的故障快速行為無法保證,因為一般來說,在不同步并發修改的情況下,無法做出任何硬性保證。失敗快速的迭代器盡力地拋出ConcurrentModificationException。因此,編寫依賴于此異常的程序的正確性將是錯誤的:迭代器的故障快速行為應僅用于檢測錯誤。
從Java 2平臺v1.2開始,這個類被改造成實現List接口,使其成為Java Collections Framework的成員。與新的集合實現不同,Vector是同步的。如果不需要線程安全的實現,建議使用ArrayList代替Vector。
7. PriorityQueue
JDK1.8官方文檔
基于優先級堆的無界優先級隊列。優先級隊列的元素根據其自然排序,或根據隊列構造時間提供的比較器進行排序,這取決于使用哪個構造函數。優先級隊列不允許空元素。依靠自然排序的優先級隊列也不允許插入不可比較的對象(否則可能導致ClassCastException)。
該隊列的頭部是相對于指定順序的最小元素。如果多個元素被綁定到最小值,那么頭就是這些元素之一 - 關系被任意破壞。隊列檢索操作輪詢,刪除,窺視和元素訪問隊列頭部的元素。
優先級隊列是無限制的,但是具有管理用于在隊列上存儲元素的數組的大小的內部容量。它始終至少與隊列大小一樣大。當元素被添加到優先級隊列中時,其容量會自動增長。沒有規定增長政策的細節。
該類及其迭代器實現了Collection和Iterator接口的所有可選方法。方法iterator()中提供的迭代器不能保證以任何特定順序遍歷優先級隊列的元素。如果需要有序遍歷,請考慮使用Arrays.sort(pq.toArray())。
請注意,此實現不同步。如果任何線程修改隊列,多線程不應同時訪問PriorityQueue實例。而是使用線程安全的PriorityBlockingQueue類。
實現注意:此實現為入隊和出隊方法(offer,poll,remove()和add)提供O(log(n))時間; remove(Object)和contains(Object)方法的線性時間;和檢索方法(窺視,元素和大小)的恒定時間。