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();
}
// 這裏不再列舉
// ......
}