java集合16-HashSet源碼走讀

轉載地址:http://blog.csdn.net/wangxiaotongfan/article/details/51346225

概要

這一章,我們對HashSet進行學習。 
我們先對HashSet有個整體認識,然後再學習它的源碼,最後再通過實例來學會使用HashSet。內容包括: 

第1部分 HashSet介紹 
第2部分 HashSet數據結構 
第3部分 HashSet源碼解析(基於JDK1.6.0_45) 
第4部分 HashSet遍歷方式 
第5部分 HashSet示例

第1部分 HashSet介紹

HashSet 簡介

HashSet 是一個沒有重複元素的集合。 
它是由HashMap實現的,不保證元素的順序,而且HashSet允許使用 null 元素。 
HashSet是非同步的。如果多個線程同時訪問一個哈希 set,而其中至少一個線程修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在創建時完成這一操作,以防止對該 set 進行意外的不同步訪問:

Set s = Collections.synchronizedSet(new HashSet(...));
  • 1

HashSet通過iterator()返回的迭代器是fail-fast的。

HashSet的構造函數
 // 默認構造函數
public HashSet() 

// 帶集合的構造函數
public HashSet(Collection<? extends E> c) 

// 指定HashSet初始容量和加載因子的構造函數
public HashSet(int initialCapacity, float loadFactor) 

// 指定HashSet初始容量的構造函數
public HashSet(int initialCapacity) 

// 指定HashSet初始容量和加載因子的構造函數,dummy沒有任何作用
HashSet(int initialCapacity, float loadFactor, boolean dummy) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

HashSet的主要API
boolean         add(E object)
void            clear()
Object          clone()
boolean         contains(Object object)
boolean         isEmpty()
Iterator<E>     iterator()
boolean         remove(Object object)
int             size()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第2部分 HashSet數據結構

HashSet的繼承關係如下:
java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractSet<E>
               ↳     java.util.HashSet<E>

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable { }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

HashSet與Map關係如下圖:

從圖中可以看出: 
(01) HashSet繼承於AbstractSet,並且實現了Set接口。 
(02) HashSet的本質是一個”沒有重複元素”的集合,它是通過HashMap實現的。HashSet中含有一個”HashMap類型的成員變量”map,HashSet的操作函數,實際上都是通過map實現的。

第3部分 HashSet源碼解析(基於JDK1.6.0_45)

爲了更瞭解HashSet的原理,下面對HashSet源碼代碼作出分析。

package java.util;

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    // HashSet是通過map(HashMap對象)保存內容的
    private transient HashMap<E,Object> map;

    // PRESENT是向map中插入key-value對應的value
    // 因爲HashSet中只需要用到key,而HashMap是key-value鍵值對;
    // 所以,向map中添加鍵值對時,鍵值對的值固定是PRESENT
    private static final Object PRESENT = new Object();

    // 默認構造函數
    public HashSet() {
        // 調用HashMap的默認構造函數,創建map
        map = new HashMap<E,Object>();
    }

    // 帶集合的構造函數
    public HashSet(Collection<? extends E> c) {
        // 創建map。
        // 爲什麼要調用Math.max((int) (c.size()/.75f) + 1, 16),從 (c.size()/.75f) + 1 和 16 中選擇一個比較大的樹呢?        
        // 首先,說明(c.size()/.75f) + 1
        //   因爲從HashMap的效率(時間成本和空間成本)考慮,HashMap的加載因子是0.75。
        //   當HashMap的“閾值”(閾值=HashMap總的大小*加載因子) < “HashMap實際大小”時,
        //   就需要將HashMap的容量翻倍。
        //   所以,(c.size()/.75f) + 1 計算出來的正好是總的空間大小。
        // 接下來,說明爲什麼是 16 。
        //   HashMap的總的大小,必須是2的指數倍。若創建HashMap時,指定的大小不是2的指數倍;
        //   HashMap的構造函數中也會重新計算,找出比“指定大小”大的最小的2的指數倍的數。
        //   所以,這裏指定爲16是從性能考慮。避免重複計算。
        map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
        // 將集合(c)中的全部元素添加到HashSet中
        addAll(c);
    }

    // 指定HashSet初始容量和加載因子的構造函數
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }

    // 指定HashSet初始容量的構造函數
    public HashSet(int initialCapacity) {
        map = new HashMap<E,Object>(initialCapacity);
    }

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }

    // 返回HashSet的迭代器
    public Iterator<E> iterator() {
        // 實際上返回的是HashMap的“key集合的迭代器”
        return map.keySet().iterator();
    }

    public int size() {
        return map.size();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    // 將元素(e)添加到HashSet中
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    // 刪除HashSet中的元素(o)
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    public void clear() {
        map.clear();
    }

    // 克隆一個HashSet,並返回Object對象
    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();
        }
    }

    // java.io.Serializable的寫入函數
    // 將HashSet的“總的容量,加載因子,實際容量,所有的元素”都寫入到輸出流中
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (Iterator i=map.keySet().iterator(); i.hasNext(); )
            s.writeObject(i.next());
    }


    // java.io.Serializable的讀取函數
    // 將HashSet的“總的容量,加載因子,實際容量,所有的元素”依次讀出
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in HashMap capacity and load factor and create backing HashMap
        int capacity = s.readInt();
        float loadFactor = s.readFloat();
        map = (((HashSet)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141

說明: HashSet的代碼實際上非常簡單,通過上面的註釋應該很能夠看懂。它是通過HashMap實現的,若對HashSet的理解有困難,建議先學習以下HashMap;學完HashMap之後,在學習HashSet就非常容易了。

第4部分 HashSet遍歷方式

4.1 通過Iterator遍歷HashSet

第一步:根據iterator()獲取HashSet的迭代器。 
第二步:遍歷迭代器獲取各個元素。

// 假設set是HashSet對象
for(Iterator iterator = set.iterator();
       iterator.hasNext(); ) { 
    iterator.next();
}   
  • 1
  • 2
  • 3
  • 4
  • 5

4.2 通過for-each遍歷HashSet

第一步:根據toArray()獲取HashSet的元素集合對應的數組。 
第二步:遍歷數組,獲取各個元素。

// 假設set是HashSet對象,並且set中元素是String類型 
String[] arr = (String[])set.toArray(new String[0]); 
for (String str:arr) 
System.out.printf(“for each : %s\n”, str); 
HashSet的遍歷測試程序如下:

import java.util.Random;
import java.util.Iterator;
import java.util.HashSet;

/*
 * @desc 介紹HashSet遍歷方法
 *
 * @author skywang
 */
public class HashSetIteratorTest {

    public static void main(String[] args) {
        // 新建HashSet
        HashSet set = new HashSet();

        // 添加元素 到HashSet中
        for (int i=0; i<5; i++)
            set.add(""+i);

        // 通過Iterator遍歷HashSet
        iteratorHashSet(set) ;

        // 通過for-each遍歷HashSet
        foreachHashSet(set);
    }

    /*
     * 通過Iterator遍歷HashSet。推薦方式
     */
    private static void iteratorHashSet(HashSet set) {
        for(Iterator iterator = set.iterator();
               iterator.hasNext(); ) {
            System.out.printf("iterator : %s\n", iterator.next());
        }
    }

    /*
     * 通過for-each遍歷HashSet。不推薦!此方法需要先將Set轉換爲數組
     */
    private static void foreachHashSet(HashSet set) {
        String[] arr = (String[])set.toArray(new String[0]);
        for (String str:arr)
            System.out.printf("for each : %s\n", str);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

運行結果:

iterator : 3
iterator : 2
iterator : 1
iterator : 0
iterator : 4
for each : 3
for each : 2
for each : 1
for each : 0
for each : 4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第5部分 HashSet示例

下面我們通過實例學習如何使用HashSet

import java.util.Iterator;
import java.util.HashSet;

/*
 * @desc HashSet常用API的使用。
 *
 * @author skywang
 */
public class HashSetTest {

    public static void main(String[] args) {
        // HashSet常用API
        testHashSetAPIs() ;
    }

    /*
     * HashSet除了iterator()和add()之外的其它常用API
     */
    private static void testHashSetAPIs() {
        // 新建HashSet
        HashSet set = new HashSet();

        // 將元素添加到Setset.add("a");
        set.add("b");
        set.add("c");
        set.add("d");
        set.add("e");

        // 打印HashSet的實際大小
        System.out.printf("size : %d\n", set.size());

        // 判斷HashSet是否包含某個值
        System.out.printf("HashSet contains a :%s\n", set.contains("a"));
        System.out.printf("HashSet contains g :%s\n", set.contains("g"));

        // 刪除HashSet中的“e”
        set.remove("e");

        // 將Set轉換爲數組
        String[] arr = (String[])set.toArray(new String[0]);
        for (String str:arr)
            System.out.printf("for each : %s\n", str);

        // 新建一個包含b、c、f的HashSet
        HashSet otherset = new HashSet();
        otherset.add("b");
        otherset.add("c");
        otherset.add("f");

        // 克隆一個removeset,內容和set一模一樣
        HashSet removeset = (HashSet)set.clone();
        // 刪除“removeset中,屬於otherSet的元素”
        removeset.removeAll(otherset);
        // 打印removeset
        System.out.printf("removeset : %s\n", removeset);

        // 克隆一個retainset,內容和set一模一樣
        HashSet retainset = (HashSet)set.clone();
        // 保留“retainset中,屬於otherSet的元素”
        retainset.retainAll(otherset);
        // 打印retainset
        System.out.printf("retainset : %s\n", retainset);


        // 遍歷HashSet
        for(Iterator iterator = set.iterator();
               iterator.hasNext(); ) 
            System.out.printf("iterator : %s\n", iterator.next());

        // 清空HashSet
        set.clear();

        // 輸出HashSet是否爲空
        System.out.printf("%s\n", set.isEmpty()?"set is empty":"set is not empty");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

運行結果:

size : 5
HashSet contains a :true
HashSet contains g :false
for each : d
for each : b
for each : c
for each : a
removeset : [d, a]
retainset : [b, c]
iterator : d
iterator : b
iterator : c
iterator : a
set is empty
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章