為何HashSet中使用PRESENT而不是null作為value
1. 為什么 HashSet 中使用 PRESENT 而不是 null 作為 value
無意之中碰到了這個問題,在此記錄一下
1.1. PRESENT 是個什么玩意
HashSet 的部分源碼如下
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(); }
1.2. HashSet 的構造方法
// 默認構造函數(shù) 底層創(chuàng)建一個HashMap public HashSet() { ?? ?// 調用HashMap的默認構造函數(shù),創(chuàng)建map ? ? map = new HashMap<E,Object>(); } ? // 帶集合的構造函數(shù) public HashSet(Collection<? extends E> c) { ?? ?// 創(chuàng)建map。 ? ? map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); ? ? // 將集合(c)中的全部元素添加到HashSet中 ? ? addAll(c); } ? // 指定HashSet初始容量和加載因子的構造函數(shù) public HashSet(int initialCapacity, float loadFactor) { ?? ?map = new HashMap<E,Object>(initialCapacity, loadFactor); } ? // 指定HashSet初始容量的構造函數(shù) public HashSet(int initialCapacity) { ?? ?map = new HashMap<E,Object>(initialCapacity); } ? HashSet(int initialCapacity, float loadFactor, boolean dummy) { ?? ?map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); }
1.3. PRESENT 何時會被用到
- add(E) 方法
- remove(Object) 方法
1.3.1. HashSet 中的 add(E) 方法
/** ?* add(E) 方法返回 null 時,表示 HashSet 添加數(shù)據(jù)成功 ?* ?* @return true 如果不包含該元素 ?*/ public boolean add(E e) { ?? ?return map.put(e, PRESENT)==null; }
直接調用的是 HashMap 的 put(K, V) 方法,此時傳入的 value 值是 PRESENT
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // tab表示 Node<K,V>類型的數(shù)組,p表示某一個具體的單鏈表 Node<K,V> 節(jié)點 Node<K,V>[] tab; Node<K,V> p; int n, i; // 判斷 table[] 是否為空,如果是空的就創(chuàng)建一個 table[],并獲取他的長度n if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // tab[i = (n - 1) & hash] 表示數(shù)組中的某一個具體位置的數(shù)據(jù) // 如果單鏈表 Node<K,V> p == tab[i = (n - 1) & hash]) == null, // 就直接 put 進單鏈表中,說明此時并沒有發(fā)生 Hash 沖突 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // 說明索引位置已經(jīng)放入過數(shù)據(jù)了,已經(jīng)在單鏈表處產(chǎn)生了Hash沖突 Node<K,V> e; K k; // 判斷 put 的數(shù)據(jù)和之前的數(shù)據(jù)是否重復 if (p.hash == hash && // 進行 key 的 hash 值和 key 的 equals() 和 == 比較,如果都相等,則初始化數(shù)組 Node<K,V> e ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 判斷是否是紅黑樹,如果是紅黑樹就直接插入樹中 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 如果不是紅黑樹,就遍歷每個節(jié)點,判斷單鏈表長度是否大于等于 7, // 如果單鏈表長度大于等于 7,數(shù)組的長度小于 64 時,會優(yōu)先選擇擴容 // 如果單鏈表長度大于等于 7,數(shù)組的長度大于 64 時,才會選擇單鏈表--->紅黑樹 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 采用尾插法,在單鏈表中插入數(shù)據(jù) p.next = newNode(hash, key, value, null); // 如果 binCount >= 8 - 1 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } // 判斷索引每個元素的key是否可要插入的key相同,如果相同就直接覆蓋 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // 此時說明 key 的 hash 值和 key 的 equals() 和 == 比較結果都相等 // 說明數(shù)組或者單鏈表中有完全相同的 key // 因此只需要將value覆蓋,并將oldValue返回即可 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } // 說明沒有key相同,因此要插入一個key-value,并記錄內部結構變化次數(shù) ++modCount; // 判斷是否擴容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
關于 HashMap 的 put(K, V) 方法的 詳細解析請看這里
- 如果 return oldValue 說明發(fā)生了 value 覆蓋,也就是說此時返回了 PRESENT,自然而然 HashMap 添加數(shù)據(jù)失敗
- 如果 return null 說明 HashMap 添加數(shù)據(jù)成功
而如果將 PRESENT 替換為 null 作為 value 值,那么 HashSet 的 add(E) 方法將無法判斷添加元素的成功與失??;因為不管是成功與失敗都會返回結果 null
1.3.2. HashMap 進行 put 元素示例
1.3.3. HashSet 中的 remove(Object) 方法
HashSet 的 remove(Object) 方法源碼
public boolean remove(Object o) { ?? ?return map.remove(o)==PRESENT; }
HashSet 的 remove(Object) 依舊直接使用 HashMap 的 remove(Object) 方法
public V remove(Object key) { ?? ?Node<K,V> e; ? ? return (e = removeNode(hash(key), key, null, false, true)) == null ? ?? ??? ?null : e.value; }
HashMap 的 remove(Object) 方法會返回 null 或 value
- 有該值,返回 value 也就是 PRESENT,表示 remove 成功
- 無該值,返回 null,自然而然 remove 失敗
而如果將 PRESENT 替換為 null 作為 value 值,那么 HashSet 的 remove(Object) 方法將無法判斷移除元素的成功與失?。灰驗椴还苁浅晒εc失敗都會返回結果 null
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
hibernate-validator改進校驗框架validator?v0.4使用
這篇文章主要為大家介紹了改進?hibernate-validator,新一代校驗框架?validator?使用介紹?v0.4,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>2023-03-03java Class.getSimpleName() 詳解及用法
這篇文章主要介紹了java Class.getSimpleName() 詳解及用法的相關資料,需要的朋友可以參考下2017-02-02Springcloud中Feign傳遞參數(shù)的過程解析
這篇文章主要介紹了Springcloud中Feign傳遞參數(shù)的過程,單個參數(shù)的傳值有兩種方式,第一種使用@RequestParam/@PathVariable進行傳值,傳遞多個參數(shù):多個參數(shù)的傳值可以使用多個@RequestParam來進行傳參,需要的朋友可以參考下2023-09-09