HashSet源碼詳解


      序言

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

                                                --WZY

 

一、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不能保證其集合中元素的順序。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM