Java容器之HashSet、LinkedHashSet、TreeSet源碼分析(不敢稱東半球最好,只稱東半球最好理解)

  前面分析了Java中的常見五大map容器,Java中的Set容器是對Map容器的封裝,今天就結合源碼分析一下Java中常見的三大Set容器。

註明:以下源碼分析都是基於jdk 1.8.0_221
在這裏插入圖片描述
\color{red}溫馨提示:請先閱讀一下我之前寫的HashMapLinkedHashMapTreeMap源碼分析博客,Java中的Set就是把Map改了個名字,這裏不會再次細講Map的相關內容。

Java容器之HashMap源碼分析(媽媽再也不用擔心我不懂HashMap了)
Java容器之LinkedHashMap源碼分析(看看確定不點進來?進來真不點?)
Java容器之TreeMap源碼分析(附紅黑樹調整圖解,全網最詳細、圖解最全,不服來辯)

一、Set容器概述

  Set,也就是我們稱的集合,與數學中的集合在概念上基本一致。數學中的集合中的元素具確定性(不存在既屬於又不屬於的集合的元素)、無序性互異性(集合中的元素沒有相同的多個),Java中的Set同樣具有確定性互異性,但是有序無序Set都有實現。HashSet集合是完全無序的(內部根據元素的hash值進行散列),LinkedHashSet是插入順序,TreeSet是元素自身大小順序。
  Java中的HashSetLinkedHashSetTreeSet類的關係如下:
在這裏插入圖片描述

二、HashSet

1、HashSet容器概述

  HashSet類的聲明如下:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

  Java中的HashSet容器是封裝了一個HashMap,元素都是存放到HashMap中,存放的形式是key-value,其中元素作key,空的Object對象作value

  下圖是JDK 1.8中的HashMap容器底層數據結構示意圖。
在這裏插入圖片描述

2、HashSet類的主要屬性

  由於元素的存取都交給了HashMap,所以HashSet非常簡單,只有兩個比較重要的屬性。

// 用於存放元素
private transient HashMap<E,Object> map;

// 由於map是存儲key-value形式的數據,所以存入的元素作key,value一律用PRESENT替代
private static final Object PRESENT = new Object();

3、HashSet類的構造器

/**
 * 默認構造器(調用HashMap的默認構造器,table數組默認長度16,負載因子默認0.75)
 */
public HashSet() {
    map = new HashMap<>();
}

/**
 * 複製構造器
 * 將容器C中的元素複製到當前創建的set容器中
 */
public HashSet(Collection<? extends E> c) {
	// c.size()/.75f) + 1的作用是計算最小需要的容量,0.75是默認負載因子
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    // copy到當前初始化的set容器
    addAll(c);
}

/**
 * @param      initialCapacity   hashmap中的table數組長度
 * @param      loadFactor        負載因子(initialCapacity * loadFactor 表示 map實際可存放的最大key-value數,超過就需要進行擴容)
 */
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

/**
 * 默認負載因子0.75
 * @param      initialCapacity   hashmap中的table數組長度table
 */
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

/**
 * 此構造器將map初始化爲一個LinkedHashMap(是HashMap的子類,可維持key-value插入的順序)
 * 注意:此構造器未使用任何修飾符修飾,默認是default,也就是在當前包下可用
 * 		後面講到LinkedHashSet時,會提及到這個構造方法
 *
 * @param      initialCapacity   LinkedHashMap中的table數組長度
 * @param      loadFactor        負載因子(initialCapacity * loadFactor 表示 map實際可存放的最大key-value數,超過就需要進行擴容)
 * @param      dummy             ignored (distinguishes this
 */
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

4、HashSet實現原理分析

  現在我們看看HashSet中的查找、插入、刪除元素是不是調用了HashMap的相關方法。

①、add方法

/**
 * 插入一個元素到set中,如果插入成功返回true,插入失敗(已經包含這個元素)返回false
 */
public boolean add(E e) {
	// 調用hashmap.put方法進行插入,key = e,value = PRESENT(前面介紹過了,HashSet類中的常量,空Object對象)
    return map.put(e, PRESENT)==null;
}

②、contains方法

/**
 * 查找元素
 */
public boolean contains(Object o) {
	// 插入時,元素作key,此時查找只需要查找key是否存在即可
    return map.containsKey(o);
}

③、remove方法

/**
 * 刪除元素,刪除成功返回true,刪除失敗(容器不再此元素)返回false
 */
public boolean remove(Object o) {
	// 調用hashmap中的remove刪除key-value
    return map.remove(o)==PRESENT;
}

三、LinkedHashSet

1、LinkedHashSet容器概述

  LinkedHashSet類的聲明如下:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

  LinkedHashSetHashSet的子類,但是初始化HashSet.map容器時,用的是LinkedHashMap容器。LinkedHashMapHashMap的子類,LinkedHashMap底層也是使用HashMap存放key-value,但是維護了一個雙鏈表,特性是可按插入順序存儲。
  因此LinkedHashSetHashSet的不同是,HashSet中存儲的元素是無序的,LinkedHashSet中的元素是按插入順序存儲的。
在這裏插入圖片描述

2、LinkedHashSet類的主要屬性

  LinkedHashSet類除了serialVersionUID(序列化id)沒有自己特有屬性,根據繼承規則,它繼承有HashSet類中的所有屬性。

3、LinkedHashSet類的構造器

/**
 * 指定LinkedHashMap的容量、負載因子
 */
public LinkedHashSet(int initialCapacity, float loadFactor) {
	// 這裏調用HashMap的構造器,HashMap、LinkedHashSet在同一包下
	// 所以可以調用HashSet(int initialCapacity, float loadFactor, boolean dummy)構造器(在HashMap中特意提及過)
    super(initialCapacity, loadFactor, true);
}

/**
 * 默認負載因子爲0.75
 */
public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}

/**
 * LinkedHashMap默認16,負載因子默認0.75
 */
public LinkedHashSet() {
    super(16, .75f, true);
}

/**
 * 複製構造器,將容器c中的元素全部複製到新建的容器中
 */
public LinkedHashSet(Collection<? extends E> c) {
    super(Math.max(2*c.size(), 11), .75f, true);
    addAll(c);
}

3、LinkedHashSet實現原理分析

  LinkedHashSet還真沒啥好分析的,作爲HashSet的子類,本身未重寫任何查找、插入、刪除相關的方法,全部調用父類HashSet中的實現。
  關於LinkedHashMap如何維護插入順序的鏈表,請參考我之前寫的LinkedHashMap源碼分析,鏈接→Java容器之LinkedHashMap源碼分析(看看確定不點進來?進來真不點?)

四、TreeSet

1、TreeSet容器概述

  Java中的TreeSet類聲明如下:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable

  Java中的TreeSet容器是對TreeMap的封裝,底層使用TreeMap存放元素。TreeMap容器底層是紅黑樹,存放key-value形式的數據,所以TreeSet中的元素作key,一個空Object對象作value。
在這裏插入圖片描述

2、TreeSet類的主要屬性

  同樣由於元素的存儲都交給了TreeMap,所以TreeSet類中的主要屬性只有兩個,非常簡潔。

/**
 * 存放key-value的TreeMap容器
 * TreeMap是NavigableMap的實現類
 */
private transient NavigableMap<E,Object> m;

// key-value形式中的value
private static final Object PRESENT = new Object();

3、TreeSet中的構造器

/**
 * 可指定NavigableMap容器
 */
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

/**
 * new TreeMap對象,調用上面的構造器
 */
public TreeSet() {
    this(new TreeMap<E,Object>());
}

/**
 * 手動指定元素的比較器(key的排序方式)
 */
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

/**
 * 複製構造器
 */
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

/**
 * 複製構造器
 */
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}

4、TreeSet實現原理分析

  TreeSet重寫了查找、插入、刪除的相關方法,不過只是對TreeMap的相關方法就行封裝。

①、add方法

/**
 * 插入一個元素到set中,如果插入成功返回true,插入失敗(已經包含這個元素)返回false
 */
public boolean add(E e) {
	// 調用hashmap.put方法進行插入,key = e,value = PRESENT(前面介紹過了,HashSet類中的常量,空Object對象)
    return map.put(e, PRESENT)==null;
}

②、contains方法

/**
 * 查找元素
 */
public boolean contains(Object o) {
	// 插入時,元素作key,此時查找只需要查找key是否存在即可
    return map.containsKey(o);
}

③、remove方法

/**
 * 刪除元素,刪除成功返回true,刪除失敗(容器不再此元素)返回false
 */
public boolean remove(Object o) {
	// 調用hashmap中的remove刪除key-value
    return map.remove(o)==PRESENT;
}

  關於TreeMap是如何維護紅黑樹的,請參考我之前的TreeMap源碼分析博客,鏈接→Java容器之TreeMap源碼分析(附紅黑樹調整圖解,全網最詳細、圖解最全,不服來辯)

五、總結

  Java中的Set是對Map容器的封裝,主要是因爲Map支持key-value的存儲,滿足Set集合的特性,Set支持元素的存儲,所以只需要實現Map相關容器,再封裝一下就是Set容器了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章