public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{ static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<E,Object>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(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(); } 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(); } } 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 (Iterator i=map.keySet().iterator(); i.hasNext(); ) s.writeObject(i.next()); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read in HashMap capacity and load factor and create backing HashMap int capacity = s.readInt(); float loadFactor = s.readFloat(); map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); // Read in size int size = s.readInt(); // Read in all elements in the proper order. for (int i=0; i<size; i++) { E e = (E) s.readObject(); map.put(e, PRESENT); } }}
hashSet實現了Set介面,hashSet不保證迭代器的順序特別是不保證順序永久不變,hashSet中只允許存在一個null元素,對於HashSet而言,它是基於HashMap來實現的,底層採用HashMap來儲存元素。HashSet是 非同步的 。如果多個線程同時訪問一個雜湊 set,而其中至少一個線程修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的對象執行同步操作來完成的。
如果不存在這樣的對象,則應該使用 Collections.synchronizedSet 方法來“封裝” set。最好在建立時完成這一操作,以防止對該 set 進行意外的不同步訪問。HashSet通過iterator()返回的 迭代器是fail-fast的。
HashSet屬性說明:
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
第一個常量:map,說明hashSet的底層是用hashMap來儲存資料的。
第二個常量:Present,由於set只用到了hashMap的key,所以,這裡定義了一個常量Object類,用來充當hashMap的value.
所以,就進一步論證了hashSet是用hashMap來實現的,只不過只用了hashMap的key,value用object對象來填充。
這裡new了一個object對象,為什麼不用null呢,new object是會在堆記憶體中開闢空間的,null是不會再堆記憶體開闢空間。如果聲明為null,可能會造成nullpointExcetion,所以為了避免這個,應該就開闢了一個8 byte的空間來存一個object對象的吧。
hashSet構造方法:
//使用HashMap的預設容量大小16和預設載入因子0.75初始化map,構造一個hashSet
public HashSet() {
map = new HashMap<E,Object>();
}
/**
* 構造一個指定Collection參數的HashSet,這裡不僅僅是Set,只要實現Collection介面的容器都可以
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 1, 16));
// 使用Collection實現的Iterator迭代器,將集合c的元素一個個加入HashSet中
addAll(c);
}
/**
* 使用指定的初始容量大小和載入因子初始化map,構造一個HashSet
*/
public HashSet( int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
/**
* 不對外公開的一個構造方法(預設default修飾),底層構造的是LinkedHashMap,dummy只是一個標示參數,無具體意義
*/
HashSet( int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
只有最後一個構造方法有寫區別,這裡構造的是LinkedHashMap,該方法不對外公開,實際上是提供給LinkedHashSet使用的,而第三個參數dummy是無意義的,只是為了區分其他構造方法。
boolean contains(Object o):其底層調用的是HashMap.containsKey(Object key)判斷是否包含指定key
HashSet add(E e):其調用的是底層HashMap中的put(K key, V value)方法,首先判斷元素(也就是key)是否存在,如果不存在則插入,如果存在則不插入,這樣HashSet中就不存在重複值
//如果此set中尚未包含指定元素,則添加指定元素
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
底層:當向HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然後根據該值確定對象在HashSet中的儲存位置。在Hash集合中,不能同時存放兩個相等的元素,而判斷兩個元素相等的標準是兩個對象通過equals方法比較相等並且兩個對象的HashCode方法傳回值也相等。
注意:對於HashSet中儲存的對象,請注意正確重寫其equals和hashCode方法,以保證放入的對象的唯一性。 HashSet remove(Object o)、clear():
//如果指定元素存在於此set中,則將其移除
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
//從此set中移除所有元素
public void clear() {
map.clear();
}
size()、isEmpty()
//返回此set中的元素的數量
public int size() {
return map.size();
}
//如果此set不包含任何元素,則返回 true
public boolean isEmpty() {
return map.isEmpty();
}
HashSet迭代器:
//返回對此 set中元素進行迭代的迭代器
public Iterator<E> iterator() {
return map.keySet().iterator(); //HashMap.keySet()返回<key, value>對中的key集
}
支援序列化的寫入函數writeObject(java.io.ObjectOutputStream s)和讀取函數readObject(java.io.ObjectInputStream s):
java.io.Serializable的寫入函數,將HashSet的“總的容量,載入因子,實際容量,所有的元素”都寫入到輸出資料流中
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);
}
// java.io.Serializable的讀取函數,將HashSet的“總的容量,載入因子,實際容量,所有的元素”依次讀出
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);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
//Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}