HashSet原始碼詳解

一杯涼茶發表於2016-11-29

      序言

         在寫了HashMap文章後,隔了幾天才繼續這一系列的文章,因為要學的東西實在是太多了,寫一篇要花費的時間很多,所以導致隔了幾天才來寫。不過希望自己堅持下去。終有一天會撥開雲霧見青天的。學HashSet的話,要先懂HashMap,所以如果大家如果還不懂HashMap可以先往前看一下我寫的HashMap那篇文章。http://www.cnblogs.com/whgk/p/6091316.html

                                                --WH

 

一、HashSetAPI文件

        個人感覺看英文的api對自己會有幫助,實在琢磨不透在拿出中文版的來對比一下,看看人家是如何翻譯的,這樣一來,對自己閱讀英文文件有幫助,也讓自己對該知識點更加映像深刻,你要去翻譯它,就必須先要理解他講的是什麼東西,而直接看中文文件的話,直接一筆帶過了。

      看了api之後,可以知道三點重要的東西,快速失敗這種不算陌生了,

        1、底層是用HashMap實現

        2、無序的

        3、非執行緒安全的

//這一段就告訴了我們hashset底層就是用hashMap來儲存的,那我們對HashMap就挺熟悉了,能夠儲存null值,不是按順序儲存也可以理解,因為HashMap中元素的位置就會變動。
這裡hashSet的第一個特性就是無序,現在就差不多知道了為什麼無序了把。
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration
order of the set; in particular, it does not guarantee
that the order will remain constant over time. This class permits the null element. //講解一些特點,在add、remove等操作上效能好,查詢元素時的效率跟hashset中存放的元素個數還有map的bucket數量成比例,所以不要講inital capacity設定的太高,也不能講載入印子設定的太低了 This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among
the buckets. Iterating over this set requires time proportional to the sum of the HashSet instance's size (the number of elements) plus the "capacity" of the backing HashMap instance (the number of buckets). Thus, it's very
important not to set the initial capacity too high (or the load factor too low) if iteration performance is important. //非執行緒安全的,如果多個執行緒訪問他並改變了內部結構,比如add、delete等操作,會發生快速失敗,所以解決的方案就是set物件上加同步鎖。或者在建立的時候,用Collections.SynchronizedSet這個方法修飾它。還有更下面那些就是解釋快速失敗是幹什麼的,等一些東西。 Note that this implementation is not synchronized. If multiple threads access a hash set concurrently, and at least one of the threads modifies the set, it must be

synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the set. If no such object exists, the set should
be "wrapped" using the Collections.synchronizedSet method. This is best done at creation time, to prevent accidental unsynchronized access to the set: Set s = Collections.synchronizedSet(new HashSet(...));The iterators returned by this class's iterator method are fail-fast: if the set is modified at any time after the iterator is created, in any way except through the iterator's own remove method, the Iterator throws a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future. Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs. This class is a member of the Java Collections Framework. Since:

 

 

二、HashSet繼承結構

          

           

      沒有什麼特別的東西,很常用的繼承結構,一步一步慢慢分解下來,中間用抽象類來緩衝下面實現集合的工作量

      實現的Set介面,這個是多餘的東西,可以不實現它,因為在AbstractSet中已經實現了set介面了,繼承了AbstractSet,就相當於也實現了Set介面

      Cloneable:能夠使用clone方法,目的是為了在陣列擴增大小的時候,能用到此方法

      Serializable:能夠使之序列化

 

三、HashSet構造方法

         有五個構造方法,其中API只寫出四個來,因為最後一個構造方法的訪問許可權是default。只能在本包內可見,所以API中沒有顯示出來

            

           

        看看構造方法中度幹了些什麼

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
//一個空的建構函式,new一個hashMap物件例項出來。預設初始容量是16,載入因子是0.75,這些都市HashMap的知識
public HashSet() { map = new HashMap<>(); } //講一個新的collection轉換為一個HashSet集合。這個構造和空建構函式都市規定需要寫的。 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } //自己初始化容量和載入因子的大小 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //初始化容量大小,載入因子用預設的 public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } //這個就是那個default許可權的構造方法,只能在包內可見,這個作用是給HashSet的子類 LinkedHashSet用的,LinkedHashSet中並沒有什麼別的方法,就四個構造方法,並且構造方法也是呼叫HashSet中的構造方法,也就是
呼叫這個構造方法,來使其底層的實現原理為LinkedHashMap,而不是HashMap。所以該方法的作用就在此處。
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }

 

四、HashSet常用方法

            

 

      add(E)

//這個方法就可以得知HashSet新增的元素是不能夠重複的,原因是什麼呢,set將每次新增的元素度是通過map中的key來儲存,當有相同的key時,也就是新增了相同的元素,那麼map會講value給覆蓋掉,而key還是原來的key
,所以,這就是set不能夠重複的原因。這個方法的PRESENT可以看下面的註釋,
//返回值的式子的意思很好理解,map中增加元素是先通過key查詢有沒有相同的key值,如果有,則覆蓋value,返回oldValue。沒有,則建立一個新的entry,並且返回null值。如果key等於null,也會返回null值。
  所以return會有一個==null的判斷
public boolean add(E e) { return map.put(e, PRESENT)==null; } //PERSENT:通過註釋,可以知道這個一個假的值,為了符合map的key,value的方式才用到這個值。 // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();

 

      remove(Object)

//map中通過key來移除對應的元素,如果有該key,會返回其value值,沒有,則返回null
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

//HashMap中的remove,看一眼就懂了。
    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

 

       contains(Object)

//是否包含某值,也就是判斷HashMap中是否有這個Key值。
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
//HashMap中的containsKey方法
    public boolean containsKey(Object key) {
//也就是通過key能不能找得到其entry,找得到就說明有。找不到就沒有
        return getEntry(key) != null;
    }

      isEmpty()

//非常簡單,都市通過呼叫map的方法 
   public boolean isEmpty() {
        return map.isEmpty();
    }

 

     size()

//看set中有多少個元素,也就是看map中有多少個元素
    public int size() {
        return map.size();
    }

 

    iterator()

//可以重點看一下這個迭代器,這個迭代器在HashMap中就已經構建好了。
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
//keySet這個方法也就是為了new一個KeySet這個類出來,來看看KeySet這個類
    public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }
//KeySet類是HashMap中的一個內部類,繼承了AbstractSet,所以他也是一個set,其中就是一個寫普通的操作,看一下這個newKeyIterator是什麼
   private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
//newKeyIterator
    Iterator<K> newKeyIterator()   {
        return new KeyIterator();
    }
//KeyIterator,重點來了,KeyIterator。看名字就知道,就對key進行迭代的迭代器,也就是為set量身打造的,因為set存放的元素就是存放在HashMap中的key,所以為了能夠迭代set,HashMap
就實現了這個專門遍歷key的迭代器,通過繼承HashIterator,能夠每次拿到nextEntry。也就能拿到對應的可以了,可以看下面的圖,看下HashIterator有哪些方法就明白了
private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } }

              

 

      clone()

//複製一個HashSet,但是隻是複製原HashSet的淺表副本,
    public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    什麼是淺表副本?就是隻複製其中元素的值,不復制值所對應的引用,也就是說,一個newHashSet、一個oldHashSet。他們中存放的元素是引用型別的元素,那麼淺表副本就是改變newHashSet中引用的值,不會影響大oldHashSet,簡單的說,就是兩者毫無關係,僅僅是他們元素的值相同,這裡要明白的是 值傳遞和引用傳遞。

 

五、總結

      到這裡,HashSet的原始碼就已經結束呢,如果你對HashMap很熟悉,那麼看HashSet的原始碼將會毫無阻力,現在來說說HashSet的特點把

        1、元素沒有順序(現在知道為什麼沒有順序了把,因為底層用的是HashMap,HashMap本身中的元素度沒有順序,那麼HashSet更不可能有了)

        2、元素不能重複(這個也很好理解了,因為HashSet中存放的元素,度是當作HashMap的key來使用,HashMap中key是不能相同的,所以HashSet的元素也不能重複了)  

        3、非執行緒安全。

 

      linkedHashSet和hashSet的區別

          來看一下linkedHashSet的繼承結構

                 

          他是繼承自HashSet的,那麼就有HashSet的所有功能,在來看一下linkedHashSet其中的方法

                    

          其中只有四個構造方法,每一個構造方法都市呼叫其父類中,也就是HashSet中的帶有三個引數的構造方法,上面我們已經分析過了。其底層實現就是用LinkedHashMap。所以想要知道LinkedHashSet的原理,就要先知道LinkedHashMap的實現原理。這裡我就只總結一下其特點,具體的分析,請看LinkedHashMap的原始碼分析。

          1、LinkedHashSet能按元素插入的順序進行迭代,也就是說,跟HashSet唯一的區別就是能夠保證集合中元素的順序,注意,是順序,其位置是變化的,比如:往LinkedHashSet中插入A,B,C,D, 這四個元素其儲存的位置可能是變化的,但是在輸出的時候能夠確定第一個插入的元素是A,A後面是B,說的順序就是這個意思。 HashSet就不能保證其集合中元素的順序,應為HashMap每次將元素插入的位置度不確定,並且元素度是放在桶中的,每次遍歷度是從第一個桶開始遍歷,但是第一個桶中的元素也是不固定的,所以說HashSet不能保證其集合中元素的順序。

 

相關文章