Java 集合 Set

HashSet

大多時候使用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值

clipboard.png

  • 用第一步計算出來的多個hashCode值組合計算出一個hashCode值返回

LinkedHashSet類

LinkedHashSet集合根據元素的hashCode值來決定元素的存儲位置,同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。當遍歷LinkedHashSet集合裏的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裏的元素

LinkedHashSet須要維護元素的插入順序,所以性能略低於HashSet的性能,但在迭代訪問Set裏的所有元素時將有很好的性能,由於它以鏈表來維護內部順序

雖然LinkedHashSet使用了鏈表記錄集合元素的添加順序,但LinkedHashSet依然是HashSet,所以不容許集合元素重複

TreeSet類

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的集合元素也是有序的,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集合中的全部元素必須是同一個枚舉類的枚舉值

各Set實現類的性能分析

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(...));
相關文章
相關標籤/搜索