大多時候使用Set集合時就是使用HashSet實現類。HashSet按Hash算法來存儲集合中的元素,所以具備很好的存取和查找性能java
HashSet具備如下特色算法
不能保證元素的排列順序,順序可能與添加順序不一樣,順序也有可能發生變化數組
HashSet不是同步的,若是多個線程同時訪問一個HashSet,假設有兩個或兩個以上線程同時修改了HashSet集合時,則必須經過代碼來保證其同步安全
集合元素值能夠是null數據結構
HashSet會調用該對象的hashCode()方法來獲得該對象的hashCode()值,而後根據該hashCode()值決定該對象在HashSet中的存儲位置;HashSet集合判斷兩個元素相等的標準是兩個對象經過equals()方法比較相等,而且兩個對象的hashCode()方法返回值也相等工具
hash(也被翻譯爲哈希、散列)算法的功能:
它能保證快速查找被檢索的對象,hash算法的價值在於速度。當須要查詢集合中某個元素時,hash算法能夠根據該元素的hashCode值計算出該元素的存儲位置,從而快速定位該元素。性能
爲何不直接使用數組、還須要使用HashSet?
由於數組元素的索引是連續的,並且數組的長度是固定的、沒法自由增長數組的長度。而HashSet採用每一個元素的hashCode值來計算其存儲位置,從而能夠自由增長HashSet的長度,並能夠根據元素的HashCode值來訪問元素。所以,當從HashSet中訪問元素時,HashSet先計算該元素的hashCode值(調用該對象的hashCode()方法的返回值),而後直接到該hashCode值對應的位置去取出該元素——這就是HashSet速度很快的緣由this
HashSet中每一個能存儲元素的「槽位」(slot)一般稱爲「桶」(bucket)es5
hashCode()方法的基本重寫規則spa
在程序運行過程當中,同一個對象屢次調用hashCode()方法應該返回相同的值
當兩個對象經過equals()方法比較返回true時,這兩個對象的hashCode()方法應該返回相等的值
對象中用做equals()方法比較標準的實例變量,都應該用於計算hashCode()值
hashCode()方法的基本重寫步驟
把對象內每一個有意義的實例變量(即每一個參與equals()方法比較標準的實例變量)計算出一個Int類型的hashCode值
用第一步計算出來的多個hashCode值組合計算出一個hashCode值返回
LinkedHashSet集合根據元素的hashCode值來決定元素的存儲位置,同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。當遍歷LinkedHashSet集合裏的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裏的元素
LinkedHashSet須要維護元素的插入順序,所以性能略低於HashSet的性能,但在迭代訪問Set裏的所有元素時將有很好的性能,由於它以鏈表來維護內部順序
雖然LinkedHashSet使用了鏈表記錄集合元素的添加順序,但LinkedHashSet依然是HashSet,所以不容許集合元素重複
TreeSet是SortedSet接口的實現類,能夠確保集合元素處於排序狀態。根據元素實際值的大小進行排序
TreeSet的額外方法
Comparator comparator():若是TreeSet採用了定製排序,則該方法返回定製排序所使用的Comparator;若是TreeSet採用了天然排序,則返回null
Object first():返回集合中的第一個元素
Object last():返回集合中的最後一個元素
Object lower(Object e):返回集合中位於指定元素以前的元素(即小於指定元素的最大元素,參考元素不須要是TreeSet集合裏的元素)
Object higher(Object e):返回集合中位於指定元素以後的元素(即大於指定元素的最小元素,參考元素不須要是TreeSet集合裏的元素)
SortedSet subSet(Object fromElement, Object toElement):返回此Set的子集,範圍從fromElement(包括)到toElement(不包括)
SortedSet headSet(Object toElement):返回此Set的子集,由小於toElement的元素組成
SortedSet tailSet(Object fromElement):返回此Set的子集,由大於或等於fromElement的元素組成
HashSet採用hash算法來決定元素的存儲位置,TreeSet採用紅黑樹的數據結構來存儲集合元素。
TreeSet支持兩種排序方法。在默認狀況下,TreeSet採用天然排序
import java.util.*; public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new TreeSet(); // 向TreeSet中添加四個Integer對象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); // 輸出集合元素,看到集合元素已經處於排序狀態 System.out.println(nums); // 輸出集合裏的第一個元素 System.out.println(nums.first()); // 輸出-9 // 輸出集合裏的最後一個元素 System.out.println(nums.last()); // 輸出10 // 返回小於4的子集,不包含4 System.out.println(nums.headSet(4)); // 輸出[-9, 2] // 返回大於5的子集,若是Set中包含5,子集中還包含5 System.out.println(nums.tailSet(5)); // 輸出 [5, 10] // 返回大於等於-3,小於4的子集。 System.out.println(nums.subSet(-3 , 4)); // 輸出[2] } }
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關係,而後將集合元素按升序排列,這種方式就是天然排列
compareTo(Object obj)方法返回一個整數值,實現該接口的類必須實現該方法,實現了該接口的類的對象就能夠比較大小。當一個對象調用該方法與另外一個對象進行比較時,例如obj1.compareTo(obj2),若是該方法返回0,則代表這兩個對象相等;若是該方法返回一個正整數,則代表obj1大於obj2;若是該方法返回一個負整數,則代表obj1小於obj2
實現了Comparable接口的經常使用類
BigDecimal、BigInteger以及全部的數組型對應的包裝類:按它們對應的數組大小進行比較
Character:按字符的UNICODE值進行比較
Boolean:true對應的包裝類實例大於false對應的包裝類實例
String:按字符串中字符的UNICODE值進行比較
Date、Time:後面的時間、日期比前面的時間、日期大
一個對象添加到TreeSet時,則該對象的類必須實現Comparable接口,不然程序將會拋出異常
import java.util.TreeSet; class Error{ } public class TreeSetErrorTest { public static void main(String[] args) { TreeSet treeSet = new TreeSet<>(); treeSet.add(new Error()); treeSet.add(new Error()); //① } }
添加第一個對象時,TreeSet裏沒有任何元素,因此不會出現任何問題;當添加第二個Error對象時,TreeSet就會調用該對象的compareTo(Object obj)方法與集合中的其餘元素進行比較——若是其對應的類沒有實現Comparable接口,則會引起ClassCastException異常
向TreeSet集合中添加元素時,只有第一個元素無須實現Comparable接口,後面添加的全部元素都必須實現Comparable接口
把一個對象添加到TreeSet集合時,TreeSet會調用該對象的compareTo(Object obj)方法與集合中的其餘元素進行比較。向TreeSet中添加的應該是同一個類的對象,不然也會引起ClassCastException異常
若是但願TreeSet能正常運行,TreeSet只能添加同一種類型的對象
當把一個對象加入TreeSet集合中時,TreeSet調用該對象的compareTo(Object obj)方法與容器中的其餘對象比較大小,而後根據紅黑樹結構找到它的存儲位置。若是兩個對象經過compareTo(Object obj)方法比較相等,新對象將沒法添加到TreeSet集合中
TreeSet的天然排序是根據集合元素的大小,TreeSet將它們以升序排列。若是須要實現定製排序,例如以降序排列,則能夠經過Comparator接口的幫助。
class M { int age; public M(int age) { this.age = age; } public String toString() { return "M[age:" + age + "]"; } } public class TreeSetTest4 { public static void main(String[] args) { // 此處Lambda表達式的目標類型是Comparator TreeSet ts = new TreeSet((o1 , o2) -> { M m1 = (M)o1; M m2 = (M)o2; // 根據M對象的age屬性來決定大小,age越大,M對象反而越小 return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0; }); ts.add(new M(5)); ts.add(new M(-3)); ts.add(new M(9)); System.out.println(ts); } }
EnumSet是一個專爲枚舉類設計的集合類,EnumSet中的全部元素都必須是指定枚舉類型的枚舉值,該枚舉類型在建立EnumSet時顯式或隱式地指定。EnumSet的集合元素也是有序的,EnumSet以枚舉值在Enum類內的定義順序來決定集合元素的順序
EnumSet在內部以位向量的形式存儲,EnumSet對象佔用內存很小,運行效率很好。尤爲是進行批量操做(如調用containsAll()和retainAll()方法)時,若是其參數也是EnumSet集合,則該批量操做的執行速度也很是快
EnumSet集合不容許加入null元素,不然拋出NullPointException異常
EnumSet沒有暴露任何構造器來建立該類的實例,應經過其提供的類方法來建立EnumSet對象
EnumSet allOf(Class elementType): 建立一個包含指定枚舉類裏全部枚舉值的EnumSet集合
EnumSet complementOf(EnumSet e): 建立一個其元素類型與指定EnumSet裏元素類型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此類枚舉類剩下的枚舉值(即新EnumSet集合和原EnumSet集合的集合元素加起來是該枚舉類的全部枚舉值)
EnumSet copyOf(Collection c): 使用一個普通集合來建立EnumSet集合
EnumSet copyOf(EnumSet e): 建立一個指定EnumSet具備相同元素類型、相同集合元素的EnumSet集合
EnumSet noneOf(Class elementType): 建立一個元素類型爲指定枚舉類型的空EnumSet
EnumSet of(E first,E…rest): 建立一個包含一個或多個枚舉值的EnumSet集合,傳入的多個枚舉值必須屬於同一個枚舉類
EnumSet range(E from,E to): 建立一個包含從from枚舉值到to枚舉值範圍內全部枚舉值的EnumSet集合
enum Season { SPRING,SUMMER,FALL,WINTER } public class EnumSetTest { public static void main(String[] args) { // 建立一個EnumSet集合,集合元素就是Season枚舉類的所有枚舉值 EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); // 輸出[SPRING,SUMMER,FALL,WINTER] // 建立一個EnumSet空集合,指定其集合元素是Season類的枚舉值。 EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); // 輸出[] // 手動添加兩個元素 es2.add(Season.WINTER); es2.add(Season.SPRING); System.out.println(es2); // 輸出[SPRING,WINTER] // 以指定枚舉值建立EnumSet集合 EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); System.out.println(es3); // 輸出[SUMMER,WINTER] EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER); System.out.println(es4); // 輸出[SUMMER,FALL,WINTER] // 新建立的EnumSet集合的元素和es4集合的元素有相同類型, // es5的集合元素 + es4集合元素 = Season枚舉類的所有枚舉值 EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); // 輸出[SPRING] } }
EnumSet能夠複製另外一個EnumSet集合中的全部元素來建立新的EnumSet集合,或者複製另外一個Collection集合中的全部元素來建立新的EnumSet集合。當複製Collection集合中的全部元素來建立新的EnumSet集合時,要求Collection集合中的全部元素必須是同一個枚舉類的枚舉值
HashSet的性能總比TreeSet好,特別是最經常使用的添加、查詢元素等操做。由於TreeSet須要額外的紅黑樹算法來維護集合元素的次序。只有當須要保持排序的Set時,才應該使用TreeSet,不然都應該使用HashSet
LinkedHashSet是HashSet的一個子類,對於普通的插入、刪除操做,LinkedHashSet比HashSet要略微滿意的,這是由維護鏈表所帶來的額外開銷所形成的,但因爲有了鏈表,遍歷LinkedHashSet會更快
EnumSet是全部Set實現類中性能最好的,但它只能保存同一個枚舉的枚舉值做爲集合元素
HashSet、TreeSet、EnumSet都是線程不安全的,若是有多個線程同時訪問一個Set集合,而且有超過一個線程修改了該Set集合,則必須手動保證該Set集合的同步性。一般能夠經過Collections工具類的synchronizedSortedSet方法來「包裝」該Set集合。在建立時進行,以防對Set集合的意外非同步訪問
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));