源碼閱讀(26):Java中主要的Set結構——HashSet

1、概述

在全面理解了HashMap結構後,理解HashSet數據結構也就足夠簡單了——HashSet內部就是對HashMap數據結構的依賴。HashMap結構的Key就是HashSet存儲的數據,HashMap結構的Value則是一個固定對象記爲“PRESENT”:

// ...
private static final Object PRESENT = new Object();
// ...

下圖爲HashSet的主要繼承體系:
在這裏插入圖片描述
從上圖可以看出HashSet繼承了AbstractSet,後者包括了相當一部分關於Set集合通用的方法邏輯。這些方法包括了equals(Object o)、hashCode()、isEmpty()、contains(Object o)等方法。

This class provides a skeletal implementation of the Setinterface to minimize the effort required to implement thisinterface.

在上一篇文章中《源碼閱讀(25):Java中主要的Set結構——概述》我們已經介紹過HashSet結構的使用效果,簡單來說其使用效果可以概要爲以下幾個要點:

  • HashSet中不允許存儲“相同”的元素。

如何確認“相同”元素呢?就是使用當前存儲到HashSet集合中的對象定義的hashCode()方法的返回結果作爲確認依據(關於對象定義中的equals(Object anObject)方法和hashCode()方法如何作用於對象“相等”的判定屬於最基礎知識,讀者可參考其它資料)。例如當前HashSet中存儲了java.lang.String類型的對象,那麼將使用“boolean String.hashCode()”這個方法判定對象是否“相同”。實際上這個原理也是HashMap集合的工作原理。

  • 由於內部存儲原理依賴於HashMap的原因,HashSet集合中存儲的元素並不是有序的。最顯著的效果是:同樣的元素集合,使用不同的添加順序添加到集合中,這些元素在集合中存儲的位置可能是不同的。這段代碼已經在前文中給出:
//......
// 創建一個HashSet集合,並添加字符串對象
HashSet<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set.add("1");
set.add("2");
set.add("3");
Iterator<String> itr = set.iterator();
while(itr.hasNext()) {
  String item = itr.next();
  System.out.println(String.format("item = %s" , item));
}

// 以下代碼改變了元素對象的添加順序,就會造成集合中元素的存儲順序完全不一樣
System.out.println("//=======================");
set = new HashSet<>();
set.add("1");
set.add("2");
set.add("3");
set.add("a");
set.add("b");
set.add("c");
itr = set.iterator();
while(itr.hasNext()) {
  String item = itr.next();
  System.out.println(String.format("item = %s" , item));
}
//......

輸出結果如下所示:

item = a
item = 1
item = b
item = 2
item = c
item = 3
//=======================
item = 1
item = a
item = 2
item = b
item = 3
item = c

2、結構特點

2.1、HashSet的主要結構

上文已經多次強調,HashSet集合內部依賴於HashMap集合,下面我們給出HashSet源代碼中關鍵屬性的定義代碼,進行分析:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
  // ......
  // 這是一個不參與序列化過程的HashMap集合,作爲HashSet中一個重要屬性存在。
  private transient HashMap<E,Object> map;

  // Dummy value to associate with an Object in the backing Map
  // 在HashSet基於HashMap添加元素時,後者每一個K-V鍵值對結點的Value值,都採用這個常量進行描述
  private static final Object PRESENT = new Object();
  
  // ......
}

2.2、HashSet中的構造函數

HashSet中的構造函數也很簡單,主要就是對其內部的HashMap屬性進行實例化,代碼片段如下所示:

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 * 完成一個默認的HashMap對象的實例化
 */
 public HashSet() {
   map = new HashMap<>();
 }

 /**
  * 參考一個指定集合中的元素進行內部HashMap集合的初始化
  * 注意,HashMap的初始化大小基於當前兩個數值較大的一個:源集合大小的175%和固定值16
  *
  * @param c the collection whose elements are to be placed into this set
  * @throws NullPointerException if the specified collection is null
  */
 public HashSet(Collection<? extends E> c) {
   map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
   addAll(c);
 }

 /**
  * 該構造函數可以爲內部使用的HashMap指定兩個關鍵參數,一個是HashMap的初始化大小,另一個是HashMap的負載因子
  * @param      initialCapacity   he initial capacity of the hash map
  * @param      loadFactor        the load factor of the hash map
  * @throws     IllegalArgumentException if the initial capacity is less
  *             than zero, or if the load factor is nonpositive
  */
 public HashSet(int initialCapacity, float loadFactor) {
   map = new HashMap<>(initialCapacity, loadFactor);
 }

 /**
  * 該構造函數可以爲內部使用的HashMap指定一個關鍵參數,是HashMap的初始化大小;負載因子將設定爲默認的0.75
  * @param      initialCapacity   the initial capacity of the hash table
  * @throws     IllegalArgumentException if the initial capacity is less
  *             than zero
  */
 public HashSet(int initialCapacity) {
   map = new HashMap<>(initialCapacity);
 }

 /**
  * 這個構造函數很有趣,這是一個訪問控制級別爲“包內訪問”的構造函數,事實上,它是專門給
  * HashSet的子類LinkedHashSet進行實例化使用的構造函數。我們將在後續內容中介紹LinkedHashSet容器。
  * 那麼問題來了,爲什麼不在LinkedHashSet類中定義這個構造函數呢?
  */
 HashSet(int initialCapacity, float loadFactor, boolean dummy) {
   map = new LinkedHashMap<>(initialCapacity, loadFactor);
 }

由以上分析可以看出,基本上所有我們直接使用進行HashSet集合對象初始化的構造函數,其內部都是對HashMap對象的初始化。

3、HashSet的主要方法

HashSet的主要方法都是對內部HashMap集合主要方法的封裝

請看HashSet集合的主要方法代碼:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
  // ......
  /**
   * 集合的添加操作亦是調用了HashMap的添加操作,請注意寫入的value對象,是一個常量
   * @param e element to be added to this set
   * @return <tt>true</tt> if this set did not already contain the specified
   * element
   */
  public boolean add(E e) {
    return map.put(e, PRESENT)==null;
  }
  
  /**
   * 集合的移除操作亦是調用HashMap的移除操作。
   *
   * @param o object to be removed from this set, if present
   * @return <tt>true</tt> if the set contained the specified element
   */
  public boolean remove(Object o) {
      return map.remove(o)==PRESENT;
  }

  /**
   * 集合的元素清除操作,亦是調用HashMap集合的清除操作
   */
  public void clear() {
    map.clear();
  }

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