Java集合

集合概述

所有的集合類都位於 java.util 包下。Java 5 還在java.util.concurrent包下提供了一些多線程支持的集合類。

與數組對比: 數組元素既可以是基本類型的值,也可以是對象(實際上是對象的引用變量);而集合裏只能保存對象(對象的引用變量)。

Java集合類主要由兩個接口派生而來: CollectionMap (它們是Java集合框架的根接口)
在這裏插入圖片描述
對於Set, Queue, List 集合,常用的實現類有以下幾種:HashSet, TreeSet, ArrayList, ArrayDeque(實現了Deque接口,上面的圖片沒有列出來), LinkedList等。
在這裏插入圖片描述
對於Map集合,常用的實現類有以下幾種:HashMap, TreeMap等。

Set集合

Set集合不允許包含相同的元素。
如果試圖將兩個相同的元素加入到一個Set集合中,則添加操作會失敗,add()方法返回false,其新元素也不會被加入。

HashSet類

HashSet按Hash算法來存儲集合中的元素,因此具有很好的存取和查找性能。
特點:

  • 不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發生變化
  • HashSet不是同步的。如果多個線程同時訪問同一個HashSet,假設有兩個或兩個以上的線程修改集合時,則必須通過代碼來保證其同步。
  • 集合元素值可能是null

HashSet集合判斷兩個元素相等的標準是:兩個對象通過equals()方法比較相等,並且兩個對象的hashCode()方法返回值也相等。

LinkedHashSet類

它是HashSet的子類。也是根據元素的hashCode來決定元素的存儲位置,但它同時使用鏈表維護元素的次序。這樣使得元素看起來是以插入順序保存的。

性能略低於HashSet。但在迭代訪問Set裏的全部元素時將有很好的性能,因爲它以鏈表來維護內部順序。

TreeSet類

是SortedSet接口的實現類。它可以確保集合元素處於排序狀態。它比HashSet多了一些方法。具體可以查閱API文檔。
TreeSet採用紅黑樹的數據結構來存儲集合元素。

TreeSet支持兩種排序方法:自然排序和定製排序

自然排序

TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關係,然後將元素按升序排序,這種方式就是 自然排序

Java中提供了一個Comparable接口,該接口定義了一個compareTo(Object obj)方法,該方法返回了一個整數值,實現該接口的類,就必須實現該方法,實現了該方法的類就可以比較大小。
當一個對象調用該方法與另一個對象進行比較時,例如obj1.compare(obj2),如果方法返回0,則表明這兩個對象相等;如果返回一個正整數,則表明obj1大於obj2;如果返回一個負整數,則表明obj1小於obj2;
Java中的一些常用類已經實現了Comparable接口,並提供了比較大小的標準。
例如:
BigDecimal
Character
Boolean
String
Date
Time

注意:

  • 如果試圖將一個對象添加到TreeSet中,則該對象必須實現了Comparable接口,否則程序將會拋出異常。
  • 向TreeSet集合中添加的應該是同一種類型的對象,否則也會引發ClassCastException異常

當把一個對象加入到TreeSet集合中時,TreeSet調用該對象的compareTo(Object obj)方法與容器中的其他對象比較大小,然後根據紅黑樹的數據結構找到它的存儲位置。
如果兩個對象通過compareTo(Object obj)方法比較相等,新對象將無法添加到TreeSet集合中。

注意一個問題,但需要把一個對象放入TreeSet中,需要重寫equals方法,規則如下:如果兩個對象通過equals()方法比較返回true時,這兩個對象通過compareTo(Object obj)方法應返回0。

對於TreeSet集合,判斷兩個對象是否相等的標準是:
兩個對象通過CompareTo(Object obj)方法比較是否返回0,如果返回0,則認爲他們相等。

定製排序

放入TreeSet集合的對象需要實現Comparator接口,由該Comparator對象負責集合元素的排序邏輯。
由於Comparator是一個函數式接口,因此可以使用Lambda表達式來代替Comparator對象。

EnumSet類

是一個專爲枚舉類設計的集合類。EnumSet中的所有元素都必須是指定枚舉類型的枚舉值,該枚舉類型在創建EnumSet時顯示或者隱式地指定。
EnumSet中的集合元素也是有序的。以枚舉值在Enum類中定義的順序來決定集合元素的順序。

各Set實現類的性能分析

HashSet的性能總是比TreeSet好(特別是常用的添加、查找元素等操作),因爲TreeSet需要額外的紅黑樹算法維護集合元素的順序。當需要一個保持排序的Set時,才應該使用TreeSet,否則都應該使用HashSet。

LinkedHashSet,對於普通的插入、刪除操作,LinkedHashSet比HashSet要略微慢一點,這是由維護鏈表所帶來的額外開銷造成的,但由於有了鏈表,遍歷LinkedHashSet會更快。

EnumSet是所有Set實現類中性能最好的,但它只能保存同一個枚舉類的枚舉值作爲集合元素。

另外,HashSet、TreeSet、EnumSet都是線程不安全的

List集合

List集合代表一個元素有序、可重複的集合。
集合中每個元素都有其對應的順序索引,索引從0開始。
List集合默認按元素添加順序設置元素的索引。

List判斷兩個對象相等的標準是:
List判斷兩個對象相等只要通過equals()方法比較返回true即可。

ArrayList和Vector實現類

ArrayList和Vector都是基於數組實現的類,它們封裝了一個動態的、允許再分配的Object[]數組。
ArrayList和Vector對象使用initialCapacity參數來設置該數組的長度,當向集合中添加元素的個數超出該數組的長度,它們的initialCapacity會自動增加。
如果創建空的ArrayList和Vector集合時,不指定initialCapacity參數,則Object數組的默認長度是10

如果向ArrayList和Vector集合中添加大量元素時,可使用ensureCapacity(int minCapacity)方法一次性地增加initialCapacity,可以減少分配的次數,從而提高性能。

ArrayList和Vector的區別:
Vector 是從JDK 1.0 出現的,線程安全。(不推薦使用它)
ArrayList是從JDK 1.2 出現的,線程不安全。

LinkedList類

它也是List接口的實現類。可以根據索引來隨機訪問集合中的元素。
它還實現了Deque接口,可以被當成雙端隊列來使用,因此既可以被當成來使用,也可以被當成隊列使用。

例子如下:

public static void main(String[] args) {
		LinkedList books = new LinkedList();
		// 將字符串元素加入隊列的尾巴
		books.offer("book 1");
		// 將字符串元素加入棧的頂部
		books.push("book 2");
		// 將字符串元素加入隊列的頂部(相當於棧的頂部)
		books.offerFirst("book 3");
		// 以List的方式遍歷集合
		for (int i = 0; i < books.size(); i++) {
			System.out.println(books.get(i));
		}
		// 訪問並不刪除棧頂的元素
		System.out.println(books.peekFirst());
		// 訪問並不刪除隊列的最後一個元素
		System.out.println(books.peekLast());
		// 將棧頂的元素彈出
		System.out.println(books.pop());
		// 輸出可以看到,第一個元素被刪除
		System.out.println(books);
		// 訪問並刪除隊列的最後一個元素
		System.out.println(books.pollLast());
		// 輸出可以看到,最後一個元素被刪除
		System.out.println(books);
	}

輸出結果:

book 3
book 2
book 1
book 3
book 1
book 3
[book 2, book 1]
book 1
[book 2]

性能分析

由於數組是以一塊連續的內存區來保存所有的元素,所以數組在隨機訪問時性能最好。所有的內部是以數組作爲底層實現的集合在隨機訪問時性能都比較好。
所有的內部是以鏈表作爲底層實現的集合在執行插入、刪除操作時有較好的性能。

總體而言,ArrayList的性能比LinkedList的性能要好,因此大部分時候考慮使用ArrayList。

使用List集合時的建議:

  • 如果需要遍歷List集合元素,對於ArrayList、Vector集合,應該使用隨機訪問方式(get)來遍歷集合,這樣性能更好;對於LinkedList集合,則應該採用迭代器(Iterator)來遍歷集合。
  • 如果需要經常執行插入、刪除操作來改變包含大量數據的List集合的大小,可以考慮使用LinkedList集合。使用ArrayList和Vector集合可能需要經常重新分配內部數組的大小,效果可能較差。
  • 如果有多個線程需要同時訪問List集合中的元素,可考慮使用Collections將集合包裝成線程安全的集合。

Map集合

Map用於保存具有映射關係的數據。
key和value都可以是任何引用類型的數據。
Map的key不允許重複,即同一個Map對象的任何兩個key通過equals方法比較總是返回false。

Java 8中又爲Map添加了很多方法。

Hashtable、HashMap和ConcurrentHashMap

參考文章

Hashtable和HashMap判斷兩個value相等的標準:
只要兩個對象通過equals()方法比較返回true即可。

Properties類是Hashtable類的子類。

LinkedHashMap實現類

它是HashMap的子類。
使用雙向鏈表來維護key-value對的次序。該鏈表負責維護Map的迭代順序,迭代順序與key-value對的插入順序保持一致。

SortedMap接口和TreeMap實現類

Map接口派生出了一個SortedMap子接口,TreeMap是SortedMap的實現類。

TreeMap是一個紅黑樹結構,每個key-value對作爲紅黑樹的一個節點。
TreeMap存儲key-value對(節點)時,需要根據key對節點進行排序。
TreeMap可以保證所有的key-value對處於有序狀態。

TreeMap也有兩種排序方式:

  • 自然排序: TreeMap的所有key必須實現Comparable接口,而且所有key應該是同一個類的對象,否則將會拋出ClassCaseException異常
  • 定製排序: 創建TreeMap時,傳入一個Comparator對象,該對象負責對TreeMap中的所有key進行排序。注意,採用定製排序時,不要求Map的key實現Comparable接口。

TreeMap中判斷兩個key相等的標準是
兩個key通過compareTo()方法返回0,TreeMap則認爲這兩個key是相等的。

如果使用自定義類作爲TreeMap的key,且想讓TreeMap良好工作,則應該重寫該類的equals()方法和compareTo()方法時應保持一致的返回結果:兩個key通過equals()方法比較返回true時,它們通過compareTo()方法時應該返回0。如果equals()方法和compareTo()方法的返回結果不一致,則與Map接口的規則衝突。

工具類 Collections

Java 提供了一個操作Set、List、Map等集合的工具類:Collections。
該工具類提供了大量方法對集合元素進行排序、查詢和修改等操作,還提供了將集合對象設置爲不可變、對集合對象實現同步控制等方法。

排序

可查找API文檔

查找、替換

可查找API文檔

同步控制

Collections類中提供了多個synchronizedXxx()方法,該方法可以指定集合包裝成線程同步的集合,從而可以解決多線程併發訪問集合的線程安全問題。

public static void main(String[] args) {
		// 下面程序創建了4個線程安全的集合類
		Collection c = Collections.synchronizedCollection(new ArrayList());

		List list = Collections.synchronizedList(new ArrayList());

		Set set = Collections.synchronizedSet(new HashSet());

		Map map = Collections.synchronizedMap(new HashMap());
	}

設置不可變集合

Collections通過如下三個方法來返回一個不可變類:

  • emptyXxx()方法: 返回一個空的、不可變的集合對象
  • singletonXxx()方法:返回一個只包含指定對象的(只有一個元素)、不可變的集合對象
  • unmodifiableXxx()方法:返回指定集合對象的不可變視圖
public static void main(String[] args) {
		// 創建一個空的,不可改變的List對象
		List unmodifiableList = Collections.emptyList();
		// 創建只有一個元素,且不可改變的Set對象
		Set unmodifiableSet = Collections.singleton("Test");

		Map students = new HashMap();
		students.put("1", "Tom");
		students.put("2", "Jerry");
		// 返回普通Map對象的不可改版本
		Map unmodifiableMap = Collections.unmodifiableMap(students);

		// 造成:java.lang.UnsupportedOperationException異常
		unmodifiableMap.put("3", "Bob");

	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章