Java容器學習筆記(二) Set介面及其實作類別的相關知識總結

來源:互聯網
上載者:User

在Java容器學習筆記(一)中概述了Collection的基本概念及介面實現,並且總結了它的一個重要子介面List及其子類的實現和用法。

本篇主要總結Set介面及其實作類別的用法,包括HashSet(無序不重複),LinkedHashSet(按放入順序有序不重複),TreeSet(按紅/黑樹狀結構方式有序不重複),EnumSet,ConcurrentSkipListSet(來自於java.util.concurrent包),CopyOnWriteArraySet(來自於java.util.concurrent包)等。

 

2.     Set介面及其實作類別
Set介面中方法清單:

Set集合和List集合都存放的是單個元素的序列,但是Set集合不允許集合中有重複元素(主要依賴於equals方法)。

Set介面的父介面為Collection和Iterable,直接實現該介面的子介面有SortedSet和NavigableSet。

實現Set介面的重要類有HashSet(無序不重複),LinkedHashSet(按放入順序有序不重複),TreeSet(按紅/黑樹狀結構方式有序不重複),EnumSet,ConcurrentSkipListSet(來自於java.util.concurrent包),CopyOnWriteArraySet(來自於java.util.concurrent包)。

在Set介面中沒有新增任何方法,所有方法均來自其父介面。它無法提供像List中按位存取的方法。在數學上一個集合有三個性質:確定性,互異性,無序性。

Ø  HashSet的特點、實現機制及使用方法

a)      HashSet的特點:

HashSet中存放的元素是無序的,底層是用HashMap實現的,其中key是要放入的元素,value是一個Object類型的名為PRESENT的常量,由於用到了散列函數,因此其存取速度是非常快的,在地址空間很大的情況下它的存取速度可以達到O(1)級。如果首先瞭解了HashMap的實現方法,那麼HashSet的實現是非常簡單的。

b)HashSet的實現機制:

首先需要瞭解一下散列或者雜湊的用法。我們知道,當資料量很大時hashFunction Compute的結果將會重複,按照所示的形式進行存貯。

在HashSet中有個loadFactor(負載因子),對於所示總共有11個位置,目前有4個位置已經存放,即40%的空間已被使用。

在HashSet的預設實現中,初始容量為16,負載因子為0.75,也就是說當有75%的空間已被使用,將會進行一次再散列(再雜湊),之前的散列表(數組)將被刪除,新增加的散列表是之前散列表長度的2倍,最大值為Integer.MAX_VALUE。

負載因子越高,記憶體使用量率越大,元素的尋找時間越長。

負載因子越低,記憶體使用量率越小,元素的尋找時間越短。

從可以看出,當雜湊值相同時,將存放在同一個位置,使用鏈表方式依次連結下去。

(面試官問到這個問題,當時我的回答是再雜湊,其實我並不知道HashSet真正是怎麼實現的,我只知道在學習資料結構時學習過再雜湊,就是這個雜湊表很滿時需要重建立立雜湊表,以便於存取,因為大量的值放在一個位置上就變成了鏈表的查詢了,幾乎是O(n/2)層級的,但是我沒有說出來再雜湊的過程,以及雜湊值相同時到底如何存放,所以……~~o(>_<)o ~~)。

為了說明HashSet在Java中確實如上實現,下面附上JDK中兩個重要方法的源碼:(下面源碼來自於HashMap,原因是HashSet是基於HashMap實現的)

/**     * Rehashes the contents of this map into a new array with a     * larger capacity.  This method is called automatically when the     * number of keys in this map reaches its threshold.     *     * If current capacity is MAXIMUM_CAPACITY, this method does not     * resize the map, but sets threshold to Integer.MAX_VALUE.     * This has the effect of preventing future calls.     *     * @param newCapacity the new capacity, MUST be a power of two;     *        must be greater than current capacity unless current     *        capacity is MAXIMUM_CAPACITY (in which case value     *        is irrelevant).     */    void resize(int newCapacity) {        Entry[] oldTable = table;        int oldCapacity = oldTable.length;        if (oldCapacity == MAXIMUM_CAPACITY) {            threshold = Integer.MAX_VALUE;            return;        }        Entry[] newTable = new Entry[newCapacity];        transfer(newTable);        table = newTable;        threshold = (int)(newCapacity * loadFactor);    }    /**     * Transfers all entries from current table to newTable.     */    void transfer(Entry[] newTable) {        Entry[] src = table;        int newCapacity = newTable.length;        for (int j = 0; j < src.length; j++) {            Entry<K,V> e = src[j];            if (e != null) {                src[j] = null;                do {                    Entry<K,V> next = e.next;                    int i = indexFor(e.hash, newCapacity);                    e.next = newTable[i];                    newTable[i] = e;                    e = next;                } while (e != null);            }        }    }

HashSet共實現了5個構造方法,對外提供了4個構造方法。這些方法在api中均可看到詳細使用說明。由於HashSet基於HashMap實現,我們只關心我們放入的key,value是個Object類型的常量,所以在iterator方法中使用的是HashMap的keySet方法進行迭代的。

c)HashSet的使用方法:

從HashSet的特點及實現上看,我們知道在不需要放入重複資料並且不關心放入順序以及元素是否要求有序的情況下,我們沒有任何理由不選擇使用HashSet。另外HashSet是允許放空值的。

那麼HashSet是如何保證不重複的?下面一個例子說明:

import java.util.HashSet;import java.util.Iterator;public class ExampleForHashSet {public static void main(String[] args) {HashSet<Name> hs = new HashSet<Name>();hs.add(new Name("Wang", "wu"));hs.add(new Name("Zhang", "san"));hs.add(new Name("Wang", "san"));hs.add(new Name("Zhang", "wu"));//本句輸出為2System.out.println(hs.size());Iterator<Name> it = hs.iterator();//下面輸出兩行,分別為Zhang:san和Wang:wuwhile(it.hasNext()) {System.out.println(it.next());}}}class Name {String first;String last;public Name(String first, String last) {this.first = first;this.last = last;}@Overridepublic boolean equals(Object o) {if(null == o) {return false;}if(this == o) {return true;}if(o instanceof Name) {Name name = (Name)o;//本例認為只要first相同即相等if(this.first.equals(name.first)) {return true;}}return false;}@Overridepublic int hashCode() {int prime = 31;int result = 1;//hashcode的實現一定要和equals方法的實現對應return prime*result + first.hashCode();}@Overridepublic String toString() {return first + ":" + last;}}

簡單說明一下上面的例子:

上面已經提到HashSet裡面放的元素是不允許重複的,那麼什麼樣的元素是重複呢,重複的定義是什嗎?

上面例子中實現了一個簡單的類Name類,並且重寫了equals方法與hashCode方法,那麼重複指的是equals方法嗎?equals相同就算是重複嗎?當然不是這樣的。如果我們改寫一下hashCode方法,將傳回值改為

       return prime*result + first.hashCode() + last.hashCode()

那麼HashSet中的size會變為4,但是Name(“Wang”, “wu”)和Name(“Wang”, “san”)其實用equals方法來比較的話其實是相同的。

       Name n1 = new Name("W", "x");

    Name n2 = new Name("W", "y");

    System.out.println(n1.equals(n2));

也就是說上面代碼會輸出true。

這樣我們是不是可以這樣認為:如果hashCode相同的話再判斷equals的傳回值是否為true,如果為true則相同,即上面說的重複。如果hashCode不同那麼一定是不重複的?

由此看來equals相同,hashCode不一定相同,equals和hashCode的傳回值不是絕對關聯的?當然我們實現equals方法時是要根據hashCode方法實現的,必須建立關聯關係,也就是說正常情況下equals相同,則hashCode的傳回值應該是相同的。

Ø  LinkedHashSet的特點、實現機制及使用方法

a)      LinkedHashSet的特點:

LinkedHashSet保證了按照插入順序有序,繼承自HashSet,沒有實現新的可以使用的方法。

b)      LinkedHashSet實現機制:

LinkedHashSet繼承自HashSet,構造時使用了在HashSet中被忽略的構造方法:

/**  * Constructs a new, empty linked hash set.  (This package private  * constructor is only used by LinkedHashSet.) The backing  * HashMap instance is a LinkedHashMap with the specified initial  * capacity and the specified load factor.  *  * @param      initialCapacity   the initial capacity of the hash map  * @param      loadFactor        the load factor of the hash map  * @param      dummy             ignored (distinguishes this  *             constructor from other int, float constructor.)  * @throws     IllegalArgumentException if the initial capacity is less  *             than zero, or if the load factor is nonpositive  */HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);}

由上面JDK代碼可以看出LinkedHashSet底層是使用LinkedHashMap實現的。

所以在實現上是比較簡單的,是根據dummy這個參數,我們不需要傳入,選擇構造的是HashSet還是LinkedHashSet。

c)      LinkedHashSet的使用方法:

由於LinkedHashSet繼承自HashSet,並且沒有提供額外的供使用的方法,所以在使用時與HashSet基本相同,只是面臨的是選擇的問題。我們根據需要選擇不同的資料結構來實現我們的需求。

Ø  CopyOnWriteArraySet的特點、實現機制及使用方法

a)      CopyOnWriteArraySet的特點:

CopyOnWriteArraySet是java.util.concurrent包中的一個類,繼承自AbstractSet,底層使用CopyOnWriteArrayList實現,擁有Set的特點,也具有ArrayList的特點,並且是安全執行緒的類。

b)      CopyOnWriteArraySet的實現機制:

在實現時使用了寫時拷貝的方法以及使用重入鎖實現了線程的同步,底層使用CopyOnWriteArrayList來構造出一個執行個體對象,在添加元素時調用CopyOnWriteArrayList的addIfAbsent方法保證資料不重複,其它實現與CopyOnWriteArrayList類似。

c)      CopyOnWriteArraySet的使用方法:

這仍然面臨的是一個選擇的問題,HashSet底層也是使用數組實現的,它的優點是存取效率很高,當負載因子很小時,幾乎可以達到O(1)級的存取速度,但是它不是安全執行緒的。當我們需要在多線程並發環境下使用時可以考慮使用這個類,當然為了實現安全執行緒,這不是一個唯一的方法。

Ø  TreeSet的特點、實現機制及使用方法

a)      TreeSet的特點:

TreeSet中所放的元素是有序的,並且元素是不能重複的。

b)      TreeSet的實現機制:

TreeSet是如何保持元素的有序不重複的?

首先TreeSet底層使用TreeMap實現,和HashSet一樣,將每個要放入的元素放到key的位置,value位置放的是一個Object類型的常量。

在JDK源碼中有下面一段注釋:

/**     * Constructs a new, empty tree set, sorted according to the     * natural ordering of its elements.  All elements inserted into     * the set must implement the {@link Comparable} interface.     * Furthermore, all such elements must be <i>mutually     * comparable</i>: {@code e1.compareTo(e2)} must not throw a     * {@code ClassCastException} for any elements {@code e1} and     * {@code e2} in the set.  If the user attempts to add an element     * to the set that violates this constraint (for example, the user     * attempts to add a string element to a set whose elements are     * integers), the {@code add} call will throw a     * {@code ClassCastException}. */

從注釋中可以看出保證不重複的關鍵因素不是hashCode和equals方法,而是compareTo。也就是說要加入的元素要實現Comparable介面。

c)      TreeSet的使用方法:

在總結HashSet的使用方法時,我們用到了一個例子,那麼在使用TreeSet時同樣是一個選擇的問題,我們是否要保證插入的元素有序(不是按插入順序有序,而是根據compareTo的傳回值排序)是我們選擇使用那種類型的Set的一個標準。(我不是專家,我只是菜鳥,歡迎拍磚)

Ø  ConcurrentSkipListSet的特點、實現機制及使用方法

a) ConcurrentSkipListSet的特點:

首先必須說的是這個類的名字很是讓我奇怪,就像我當時奇怪CopyOnWriteArrayList一樣,覺得這是一個比較長的名字,但是當我查了Copy-on-Write的意思時我就不再奇怪了,甚至讓我猜到了它的實現機制。

那麼Concurrent-Skip是什麼意思呢?並行跳過?

與大多數其他並發 collection 實現一樣,此類不允許使用 null 元素,因為無法可靠地將 null 參數及傳回值與不存在的元素區分開來。

b) ConcurrentSkipListSet的實現機制:

ConcurrentSkipListSet底層是使用ConcurrentSkipListMap實現的。那麼並行跳過到底是什麼意思,本人暫時不能做出總結。⊙﹏⊙b汗

c) ConcurrentSkipListSet的使用方法:

⊙﹏⊙b汗

 

部落格內容為學習時總結的內容,我只是菜鳥,未出師門,歡迎拍磚指點!!!!如發現有錯誤請在下面評論或者mail to :
bluesky_taotao@163.com

一起深入研究

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.