Java集合原始碼分析(九)——HashSet
簡介
HashSet就是一個集合,裡面不能有重複的元素,元素也是無序的。
HashSet其實就是呼叫了HashMap實現的,所有,它也不是執行緒安全的。
HashSet通過iterator()返回的迭代器是fail-fast的。
原始碼分析
由於HashSet的原始碼很短也很容易理解,這裡就不再選取分析了。
package java.util;
import java.io.InvalidObjectException;
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
// 內部的HashMap,後面的操作都是對它的呼叫
private transient HashMap<E,Object> map;
// 用於插入HashMap時的固定值
private static final Object PRESENT = new Object();
// 預設構造
public HashSet() {
map = new HashMap<>();
}
// 填入集合的構造
public HashSet(Collection<? extends E> c) {
// 這裡為什麼需要在原來的容量和16中取最大值呢?看總結
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);
}
// 採用LinkedHashMap來實現
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
// 獲取迭代器
public Iterator<E> iterator() {
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);
}
// 新增元素,這裡新增的值就是上面定義的預設值
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();
}
// 克隆函式,直接用map的clone函式,將原來的map克隆到新的map
@SuppressWarnings("unchecked")
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(e);
}
}
// 將set輸出為序列化
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 (E e : map.keySet())
s.writeObject(e);
}
// 將輸入寫入set
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// 計算容量
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// 建立一個新的內部map
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// 依次寫入集合元素
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
// 實現了分割迭代,多執行緒用
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
}
總結
HashSet內部都是對HashMap的呼叫,不同的Key值,相同的value值。
可以通過迭代器和for-each方法對其進行遍歷。
為什麼在初始化的時候有map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
?
- 首先HashMap的預設初始容量是16,如果填入集合的大小經過變換之後還小於預設初始容量,那麼就直接使用預設初始容量。
- HashMap的預設載入因子就是0.75,當HashMap的“閾值”(閾值=HashMap總的大小*載入因子) < “HashMap實際大小”時,就會將容量翻倍,所以 c.size()/.75f) + 1 計算出來的正好是總的空間大小。
相關文章
- Java集合原始碼學習(4)HashSetJava原始碼
- HashSet原始碼分析原始碼
- Java容器類框架分析(5)HashSet原始碼分析Java框架原始碼
- Java類集框架 —— HashSet、LinkedHashSet原始碼分析Java框架原始碼
- java基礎:HashSet/LinkedHashSet/TreeSet — 原始碼分析Java原始碼
- 【Java集合】ArrayList原始碼分析Java原始碼
- JAVA集合:ArrayList原始碼分析Java原始碼
- Java集合系列-HashSetJava
- Java集合原始碼分析(十四):TreeMapJava原始碼
- Java 集合框架------ArrayList原始碼分析Java框架原始碼
- java集合原始碼分析(六):HashMapJava原始碼HashMap
- java集合原始碼分析(三):ArrayListJava原始碼
- 原始碼分析–HashSet(JDK1.8)原始碼JDK
- JAVA ArrayList集合底層原始碼分析Java原始碼
- Java 集合系列之 LinkedList原始碼分析Java原始碼
- Java集合乾貨——CopyOnWriteArrayList原始碼分析Java原始碼
- Java集合原始碼分析之開篇Java原始碼
- Java原始碼分析:Guava之不可變集合ImmutableMap的原始碼分析Java原始碼Guava
- HashSet 新增/遍歷元素原始碼分析原始碼
- Java面試題 從原始碼角度分析HashSet實現原理?Java面試題原始碼
- 死磕 java集合之LinkedList原始碼分析Java原始碼
- 死磕 java集合之ConcurrentLinkedQueue原始碼分析Java原始碼
- 死磕 java集合之PriorityQueue原始碼分析Java原始碼
- 死磕 java集合之TreeSet原始碼分析Java原始碼
- 死磕 java集合之WeakHashMap原始碼分析JavaHashMap原始碼
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- 死磕 java集合之HashMap原始碼分析JavaHashMap原始碼
- 死磕 java集合之CopyOnWriteArrayList原始碼分析Java原始碼
- 死磕 java集合之LinkedHashMap原始碼分析JavaHashMap原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- Java 集合 ArrayList 原始碼分析(帶著問題看原始碼)Java原始碼
- Java Set 常用集合 HashSet、LinkedHashSet、TreeSetJava
- 【Java集合原始碼剖析】Java集合框架Java原始碼框架
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)JavaHashMap原始碼
- 搞懂 HashSet & LinkedHashSet 原始碼以及集合常見面試題目原始碼面試題
- Java集合 HashSet的原理及常用方法Java