老規矩了,先看示例demo,再聊聊源碼
import java.util.HashSet;
import java.util.Iterator;
public class HashSetTest {
public static void main(String[] args) {
HashSet hashset = new HashSet();
System.out.println("HashSet是否爲空\t "+hashset.isEmpty());
hashset.add("hello world");
System.out.println("添加重複元素\t"+hashset.add("hello world"));
System.out.println("添加元素\t "+hashset.add("world hello"));
System.out.println("添加元素null\t"+hashset.add(null));
System.out.println("HashSet是否爲空\t "+hashset.isEmpty());
System.out.println("HashSet大小"+hashset.size());
System.out.println("HashSet是否含有‘hello world’\t"+hashset.contains("hello world"));
System.out.println("HashSet是否含有‘world’\t"+hashset.contains("world"));
System.out.println("刪除元素'hello world'\t"+hashset.remove("hello world"));
System.out.println("刪除本不存在元素'world'\t"+hashset.remove("world"));
System.out.println("迭代器遍歷");
Iterator iterator=hashset.iterator();
int i=1;
while(iterator.hasNext())
{
System.out.println("這是第"+i+"個元素\t"+iterator.next());
i++;
}
System.out.println("清空HashSet");
hashset.clear();
System.out.println("HashSet是否爲空\t "+hashset.isEmpty());
System.out.println("HashSet大小"+hashset.size());
}
}
其結果如圖
從運行的結果可以看出,HashSet有這麼幾個特點
1. 其集合元素都是唯一的,不存在重複的元素
2. 其集合元素中可以存在null,但有且只能有一個
3. 可以從迭代器遍歷的結果中可以看出,HashSet是不會維護數據插入的順序的,也不會對數據進行排序
好了,說完了特性,我們來聊聊其實現的源碼吧!
1. 類繼承結構
可以看出,HashSet主要繼承了AbstractSet類(Set的抽象類),實現了Serializable(序列化接口)、Cloneable(克隆接口)、Set的接口
需要注意的是,HashSet其實現的常用方法都是覆蓋了AbstractSet類中的方法,其實現都是調用了HashMap中的方法(這點下文有說明),不太明白的則是,爲什麼AbstractSet繼承實現了Set接口,而HashSet也繼承了Set接口(大概原因是類中的實現不能被繼承下去吧???)(有讀者知道這個答案的,請一定告知一聲,鄙人感謝不盡!)
2. 類屬性
static final long serialVersionUID = -5024744406713321676L; //序列化ID
private transient HashMap<E,Object> map; //底層數據結構是HashMap實現
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); //定義一個虛擬的object對象作爲 HashMap 的value
其屬性值以及其應用意義在後面註釋中有說明了,因此不再另外闡述
注1:由於HashSet繼承實現了Set接口,而Set規定了其集合中元素的唯一性,且也有需要有null值的需求,因此這才採用了HashMap作爲其底層的數據結構。在這點上就體現了一個道理即“先有需求,後有其架構、實現“,望各位讀者能明白,先分析需求才能完成後面的環節
注2:PRESENT的object對象是作爲hashmap中的value而存在的,由於Set是一個單值集合,且具有唯一性,因此其map中的key都對應到該object上,即所有的key對應一個共有的object上,因此其前的修飾符爲static
3. 構造函數
HashSet的構造函數比較多,且適應不同的用途
public HashSet() {
map = new HashMap<>();
}
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);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
1. 無參構造函數
無參構造函數也比較簡單,直接初始化new一個空的容量爲16,裝載因子爲0.75的HashMap對象實例,並將其引用複製給map作爲其實現的底層數據結構
2. 有參構造函數
2.1 初始容量的有參構造函數(int initialCapacity)
根據指定的容量值初始化一個HashMap實例,並將其引用複製給map作爲其實現的底層數據結構
2.2 集合對象的有參構造函數(Collection <? extends E> c)
將集合存在的元素大小(而非集合的容量)除以0.75然後加1得到的數字和16進行比較,取其最大值,作爲指定容量,初始化一個HashMap實例,調用addAll函數將集合元素添加進去
2.3 初始容量和裝載因子有參構造函數(int initialCapacity,float loadFactor)
根據指定的容量大小和裝載因子大小來初始化一個HashMap實例,並將其引用複製給map作爲其實現的底層數據結構
2.4 初始容量和裝載因子和標記的有參構造函數(int initialCapacity,float loadFactor,boolean dummy)
根據指定的容量大小和裝載因子大小來初始化一個LinkedHashMap實例,並將其引用複製給map作爲其實現的底層數據結構
注1:值得注意的是,其方法前沒有任何修飾符,表示其爲包權限的,對其不訪問公開
Q1:爲什麼HashMap的容量爲16,裝載因子爲0.75?
Q2:當以集合爲形參調用有參構造方法時,爲什麼需要以集合大小除以0.75+1後和16比較,取其大值爲容量初始化一個HashMap?
Q3:爲什麼有一個不對外公開的構造函數,且其底層結構爲LinkedHashMap?僅僅是爲了支持LinkedHashMap麼?如果是爲了支持LinkedHashMap,函數訪問權限又爲何不對外公開?其dummy參數又是什麼意思?
4. 常用方法
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
從源碼可以看出,由於採用了HashMap作爲其底層數據結構,所以常用的方法中都直接調用了map中的常用方法,這樣一來,代碼精簡,功能也實現了,不得不佩服其作者高超編碼能力和架構設計能力
4.1 size函數
調用了map中的size方法,返回其大小
4.2 isEmpty
調用了map中的isEmpty方法,返回其是否爲空的結果
4.3 contains
傳入object對象引用,調用了map中的containsKey方法,返回其是否包含有該object元素
4.4 clear
調用了map的clear方法,清除其所有元素
需要注意的是,add方法和remove方法
4.5 add方法
通過傳入的E元素值作爲key值,其PRESENT作爲value值,通過調用了map的put方法,完成元素的添加,返回其值和null值對比後的結果
4.6 remove方法
傳入object值,調用map的remove方法,和其PRESENT對比,返回其真假值
5. 常見的問題
5.1 HashSet有何特性?
5.2 HashSet的唯一性和null值是如何確保的?
5.3 爲什麼採用HashMap作爲其底層數據結構?
5.4 HashSet是不是線程安全的?如果不是?Set線程安全的有哪些?
5.5 HashSet是不是維持其輸入順序的?如果不是?可採用哪個類維持其Set的輸入順序?
5.6 HashSet是不是有序的?如果不是?可採用哪個Set有序?