今天的主角是HashSet,Set是什麼東東,當然也是一種java容器了。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
我們看到HashSet繼承了AbstractSet抽象類,並實現了Set、Cloneable、Serializable接口。AbstractSet是一個抽象類,對一些基礎的set操作進行封裝。繼續來看下Set接口的定義:
public interface Set<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
}
發現了什麼,Set接口和java.util.List接口一樣也實現了Collection接口,但是Set和List所不同的是,Set沒有get等跟下標先關的一些操作方法,那怎麼取值呢?Iterator還記得嗎,使用迭代器對不對。(不明白的回去看Iterator講解)
2.底層存儲
// 底層使用HashMap來保存HashSet的元素
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
// 由於Set只使用到了HashMap的key,所以此處定義一個靜態的常量Object類,來充當HashMap的value
private static final Object PRESENT = new Object();
看到這裏就明白了,和我們前面說的一樣,HashSet是用HashMap來保存數據,而主要使用到的就是HashMap的key。
看到private static final Object PRESENT = new Object();不知道你有沒有一點疑問呢。這裏使用一個靜態的常量Object類來充當HashMap的value,既然這裏map的value是沒有意義的,爲什麼不直接使用null值來充當value呢?比如寫成這樣子privatefinal Object PRESENT = null;我們都知道的是,Java首先將變量PRESENT分配在棧空間,而將new出來的Object分配到堆空間,這裏的new Object()是佔用堆內存的(一個空的Object對象佔用8byte),而null值我們知道,是不會在堆空間分配內存的。那麼想一想這裏爲什麼不使用null值。想到什麼嗎,看一個異常類java.lang.NullPointerException, 噢買尬,這絕對是Java程序員的一個噩夢,這是所有Java程序猿都會遇到的一個異常,你看到這個異常你以爲很好解決,但是有些時候也不是那麼容易解決,Java號稱沒有指針,但是處處碰到NullPointerException。所以啊,爲了從根源上避免NullPointerException的出現,浪費8個byte又怎麼樣,在下面的代碼中我再也不會寫這樣的代碼啦if (xxx == null) { … } else {….},好爽。
3.構造方法
/**
* 使用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);
}
/**
* 使用指定的初始容量大小和默認的加載因子0.75初始化map,構造一個HashSet
*/
public HashSet( int initialCapacity) {
map = new HashMap<E,Object>(initialCapacity);
}
/**
* 不對外公開的一個構造方法(默認default修飾),底層構造的是LinkedHashMap,dummy只是一個標示參數,無具體意義
*/
HashSet( int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
從構造方法可以很輕鬆的看出,HashSet的底層是一個HashMap,理解了HashMap後,這裏沒什麼可說的。只有最後一個構造方法有寫區別,這裏構造的是LinkedHashMap,該方法不對外公開,實際上是提供給LinkedHashSet使用的,而第三個參數dummy是無意義的,只是爲了區分其他構造方法。
4.增加和刪除
/**
* 利用HashMap的put方法實現add方法
*/
public boolean add(E e) {
return map .put(e, PRESENT)== null;
}
/**
* 利用HashMap的remove方法實現remove方法
*/
public boolean remove(Object o) {
return map .remove(o)==PRESENT;
}
/**
* 添加一個集合到HashSet中,該方法在AbstractCollection中
*/
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
// 取得集合c迭代器Iterator
Iterator<? extends E> e = c.iterator();
// 遍歷迭代器
while (e.hasNext()) {
// 將集合c的每個元素加入到HashSet中
if (add(e.next()))
modified = true;
}
return modified;
}
/**
* 刪除指定集合c中的所有元素,該方法在AbstractSet中
*/
public boolean removeAll(Collection<?> c) {
boolean modified = false;
// 判斷當前HashSet元素個數和指定集合c的元素個數,目的是減少遍歷次數
if (size() > c.size()) {
// 如果當前HashSet元素多,則遍歷集合c,將集合c中的元素一個個刪除
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
} else {
// 如果集合c元素多,則遍歷當前HashSet,將集合c中包含的元素一個個刪除
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
5.是否包含
/**
* 利用HashMap的containsKey方法實現contains方法
*/
public boolean contains(Object o) {
return map .containsKey(o);
}
/**
* 檢查是否包含指定集合中所有元素,該方法在AbstractCollection中
*/
public boolean containsAll(Collection<?> c) {
// 取得集合c的迭代器Iterator
Iterator<?> e = c.iterator();
// 遍歷迭代器,只要集合c中有一個元素不屬於當前HashSet,則返回false
while (e.hasNext())
if (!contains(e.next()))
return false;
return true;
由於HashMap基於hash表實現,hash表實現的容器最重要的一點就是可以快速存取,那麼HashSet對於contains方法,利用HashMap的containsKey方法,效率是非常之快的。在我看來,這個方法也是HashSet最核心的賣點方法之一。
6.容量檢查
/**
* Returns the number of elements in this set (its cardinality).
*
* @return the number of elements in this set (its cardinality)
*/
public int size() {
return map .size();
}
/**
* Returns <tt>true</tt> if this set contains no elements.
*
* @return <tt> true</tt> if this set contains no elements
*/
public boolean isEmpty() {
return map .isEmpty();
}
/**
* Returns an iterator over the elements in this set. The elements
* are returned in no particular order.
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
return map .keySet().iterator();
}
我cha,咋回事,HashSet的iterator()方法竟然也是利用HashMap實現的,我們去看看HashMap的keySet()方法是什麼鬼。
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
HashMap的keySet()方法的返回值竟然是一個Set,具體實現是一個叫KeySet的東東,KeySet又是什麼鬼。
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size ;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap. this.clear();
}
}
哦,KeySet是一個實現了AbstractSet的HashMap的內部類。而KeySet的iterator()方法返回的是一個newKeyIterator()方法,好繞好繞,頭暈了。
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
newKeyIterator()方法返回的又是一個KeyIterator()方法,what are you 弄啥嘞?
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
好吧,不想說什麼了,繼續往下看吧。
private abstract class HashIterator<E> implements Iterator<E> {
// 下一個需要返回的節點
Entry<K,V> next; // next entry to return
int expectedModCount ; // For fast-fail
int index ; // current slot
// 當前需要返回的節點
Entry<K,V> current;// current entry
HashIterator() {
expectedModCount = modCount ;
if (size > 0) { // advance to first entry
Entry[] t = table;
// 初始化next參數,將next賦值爲HashMap底層的第一個不爲null節點
while (index < t.length && ( next = t[index ++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 取得HashMap底層數組中鏈表的一個節點
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
// 將next指向下一個節點,並判斷是否爲null
if ((next = e.next) == null) {
Entry[] t = table;
// 如果爲null,則遍歷真個數組,知道取得一個不爲null的節點
while (index < t.length && ( next = t[index ++]) == null)
;
}
current = e;
// 返回當前節點
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key ;
current = null;
HashMap. this.removeEntryForKey(k);
expectedModCount = modCount ;
}
}
private abstract class LinkedHashIterator<T> implements Iterator<T> {
// header.after爲LinkedHashMap雙向鏈表的第一個節點,因爲LinkedHashMap的header節點不保存數據
Entry<K,V> nextEntry = header .after;
// 最後一次返回的節點
Entry<K,V> lastReturned = null;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap. this.remove(lastReturned .key);
lastReturned = null;
expectedModCount = modCount ;
}
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
// 將要返回的節點nextEntry賦值給lastReturned
// 將nextEntry賦值給臨時變量e(因爲接下來nextEntry要指向下一個節點)
Entry<K,V> e = lastReturned = nextEntry ;
// 將nextEntry指向下一個節點
nextEntry = e.after ;
// 放回當前需返回的節點
return e;
}
}