日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java HashSet源码解析

發(fā)布時(shí)間:2025/3/20 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java HashSet源码解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本解析源碼來自JDK1.7,HashSet是基于HashMap實(shí)現(xiàn)的,方法實(shí)現(xiàn)大都直接調(diào)用HashMap的方法
另一篇HashMap的源碼解析文章

概要

  • 實(shí)現(xiàn)了Set接口,實(shí)際是靠HashMap實(shí)現(xiàn)的
  • 不保證遍歷時(shí)的順序,不保證集合順序的不變性
  • HashSet允許出現(xiàn)null值
  • 假定Hash算法能很好的分散元素,查詢的時(shí)間復(fù)雜度為O(1)
  • 遍歷的時(shí)間復(fù)雜度由set的size和其依靠的HashMap的capacity來決定
  • HashSet是非同步的可以通過Set s = Collections.synchronizedSet(new HashSet(...));的方式獲得同步的set
  • HashSet的Iterator有fast-fail機(jī)制,但是并不能保證程序一定正確,fail-fast機(jī)制通常只用來檢測bug

實(shí)現(xiàn)接口

  • Set接口包含集合常用方法
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable public interface Set<E> extends Collection<E> {// Query Operationsint size();boolean isEmpty();boolean contains(Object o);Iterator<E> iterator();Object[] toArray();<T> T[] toArray(T[] a);// Modification Operationsboolean add(E e);boolean remove(Object o);// Bulk Operationsboolean containsAll(Collection<?> c);boolean addAll(Collection<? extends E> c);boolean retainAll(Collection<?> c);boolean removeAll(Collection<?> c);void clear();// Comparison and hashingboolean equals(Object o);int hashCode(); }
  • Cloneable 對集合元素進(jìn)行淺拷貝,調(diào)用了map的clone方法來克隆自身map域,由于HashMap執(zhí)行的是淺拷貝,雖然創(chuàng)建了新的Entry,但是沒有創(chuàng)建新的key和value,通過原Set和clone后的set對key和value的改變是等效的。
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();}}
  • Serializable HashSet實(shí)現(xiàn)了自己readObject和writeObject方法,將map的keySet中的元素分別寫入,對端讀出后放入自己的map中去

主要成員

  • map用來裝載set的元素,HashSet就是HashMap的keySet。transient表明序列化時(shí)略過,HashSet實(shí)現(xiàn)了自己的序列化方法。
  • 常量PRESENT用來填充HashMap的value,也就是說HashSet中的map的value都是同一個對象,高效的利用堆空間

NOTE:所有被裝入集合(map,set,list)的對象,都只是將對象的引用復(fù)制一份到集合中。如果通過外部引用改變了對象的內(nèi)容,集合中的對象的內(nèi)容也會跟著改變。但是如果將外部引用指向其他對象,集合內(nèi)部的引用并不會改變,還是會指向加入集合時(shí)引用指向的對象。也即元素被放入集合時(shí),執(zhí)行的是淺拷貝,引用復(fù)制而對象不會重新創(chuàng)建

private transient HashMap<E,Object> map;// Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();

構(gòu)造函數(shù)

我們看到初始化HashSet就是初始化對應(yīng)的HashMap對象map成員變量

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);}

集合相關(guān)方法

可以看到對HashSet的操作就是對對應(yīng)的HashMap對象map的操作

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();}

HashMap解決擴(kuò)容問題

  • 調(diào)整的時(shí)機(jī) (負(fù)載因子)x(容量)>(Map 大小),則調(diào)整 Map大小 為之前的二倍,該過程包含了table的復(fù)制,性能消耗較大,如果map大小已知,可以在初始化時(shí)合理設(shè)定map的初始大小,避免擴(kuò)容。
  • 如果數(shù)組大小已經(jīng)到達(dá)最大容量,將閾值置為Integer.MAX_VAlUE,不再進(jìn)行擴(kuò)容
  • 新申請數(shù)組,重新定址并將原數(shù)組中的Entry轉(zhuǎn)移到新數(shù)組中,由于容量變化,即使Hash值不變,Entry的index也會改變,index=hash&(length-1),取hash的低位,length增大,index取的位數(shù)增多
  • 依舊使用頭插法將所有元素進(jìn)行復(fù)制
void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}Entry[] newTable = new Entry[newCapacity];transfer(newTable, initHashSeedAsNeeded(newCapacity));table = newTable;threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);} /*** Transfers all entries from current table to newTable.*/void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for (Entry<K,V> e : table) {while(null != e) {Entry<K,V> next = e.next;if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;}}}

HashMap 元素存儲位置的計(jì)算 hash值

  • String類型的key的hashcode是根據(jù)與字符串內(nèi)容相關(guān)的,由于可能引起很多碰撞,所以值單獨(dú)計(jì)算
  • Object類型的key的HashCode是基于其內(nèi)存地址的。為了充分利用Integer值的高位,需要將高位的影響引入低位,(由于多數(shù)map的length是比較小的)
  • 由于length是2的指數(shù)倍,所以可以用hash&(length-1)代替 hash%length,位運(yùn)算有更高的效率
final int hash(Object k) {int h = hashSeed;if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);} static int indexFor(int h, int length) {// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";return h & (length-1);}

HashMap的put方法

下面以HashMap的put方法為例對HashSet的集合方法進(jìn)行說明

  • 如果是key為null,遍歷查找table中key是否有null,如果有更新value,否則添加null,value節(jié)點(diǎn)
  • 如果key不為null,根據(jù)Key的hashcode獲取Hash值,根據(jù)Hash計(jì)算其在table中的索引。hash值計(jì)算時(shí)利用高位與低位進(jìn)行異或操作,加入高位因素,來減少Hash碰撞。
  • 由于tablelength 都是2的指數(shù)次冪,所以indexFor用 HashCode&(table.lenght-1)取HashCode的低位,使用位運(yùn)算提高運(yùn)算效率
  • 如果table[i]不為null(并不表示Hash值相同,HashCode不同也可能碰撞),也就是發(fā)生了Hash碰撞,如果存在與keyHash相等(equals)或相同(==)的key,那么更新value
  • 如果table[i]為null,或者table[i]鏈表中不存在Hash值與Key相同且equals函數(shù)返回true的情況就根據(jù)Hash值添加新的節(jié)點(diǎn)
  • addEntry()方法首先判斷大小是否超過閾值,然后使用頭插法,插入元素
    NOTE
    在判斷插入Entry是否為覆蓋時(shí),會先判斷Key的hashCode是否和map中的key相等,然后判斷Equals方法或者==,所以如果重寫了equals方法,要記得重寫hashcode方法,使得其邏輯相同,否則即使equals方法判斷相等也不會發(fā)生覆蓋
public V put(K key, V value) {// HashMap允許存放null鍵和null值。// 當(dāng)key為null時(shí),調(diào)用putForNullKey方法,將value放置在數(shù)組第一個位置。if (key == null)return putForNullKey(value);// 根據(jù)key的keyCode重新計(jì)算hash值。int hash = hash(key);//注意這里的實(shí)現(xiàn)是jdk1.7和以前的版本有區(qū)別的// 搜索指定hash值在對應(yīng)table中的索引。int i = indexFor(hash, table.length);// 如果 i 索引處的 Entry 不為 null,通過循環(huán)不斷遍歷 e 元素的下一個元素。for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}// 如果i索引處的Entry為null,表明此處還沒有Entry。modCount++;// 將key、value添加到i索引處。addEntry(hash, key, value, i);return null; } /**產(chǎn)生哈希碼*/ final int hash(Object k) {int h = 0;if (useAltHashing) {if (k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h = hashSeed;}h ^= k.hashCode();// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor)./*加入高位計(jì)算,防止低位不變,高位變化是引起hash沖突*/h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);} /**產(chǎn)生索引,由于索引產(chǎn)生是不確定的,因此也就造成了HashMap順序的不確定性。需要注意的是不同的hash產(chǎn)生的索引完全有可能相同的該方法的實(shí)現(xiàn)十分的巧妙,它通過h & (length-1)來的到對象保存的索引,有可知道底層數(shù)組為2的n次方,這在速度上就有了明顯的優(yōu)化*/ static int indexFor(int h, int length) {return h & (length-1);}private V putForNullKey(V value) {for (Entry<K,V> e = table[0]; e != null; e = e.next) {if (e.key == null) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(0, null, value, 0);return null;}void addEntry(int hash, K key, V value, int bucketIndex) {if ((size >= threshold) && (null != table[bucketIndex])) {resize(2 * table.length);hash = (null != key) ? hash(key) : 0;bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);} void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;}

Clone方法

Clone方法是淺拷貝方法
Clone就是將對應(yīng)的map進(jìn)行復(fù)制

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();}}

HashMap的Clone方法

Clone實(shí)現(xiàn)的是淺拷貝,雖然重新創(chuàng)建了Entry但是并沒有重新創(chuàng)建key,value,即如果通過原HashMap的key的引用改變了key的屬性,clone出來的HashMap的key也會跟著改變,克隆出來的Map的數(shù)組的大小也不一定與原Map相同

  • HashSet的Clone方法其實(shí)就是對成員變量map的clone
  • 首先會創(chuàng)建一個空的HashMap對象
  • 然后對該HashMap進(jìn)行擴(kuò)容,容量大小取Math.min(當(dāng)前table大小,HashMap的最大容量,當(dāng)前的Size*(Math.min(1/loadFactor,4)),克隆出來的HashMap的數(shù)組初始大小并不會與當(dāng)前Map一致,而是考慮合理的初始化loadFactor之后的結(jié)果。
  • 最后調(diào)用putAllForCreate(this)依次將當(dāng)前Map的(key,value)放到Map中去,過程中雖然創(chuàng)建了新的Entry但是并沒有創(chuàng)建新的key,value,通過原HashMap和通過克隆出來的HashMap改變(key,value)效果是等同的。
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();}} public Object clone() {HashMap<K,V> result = null;try {result = (HashMap<K,V>)super.clone();} catch (CloneNotSupportedException e) {// assert false;}if (result.table != EMPTY_TABLE) {result.inflateTable(Math.min((int) Math.min(size * Math.min(1 / loadFactor, 4.0f),// we have limits...HashMap.MAXIMUM_CAPACITY),table.length));}result.entrySet = null;result.modCount = 0;result.size = 0;result.init();result.putAllForCreate(this);return result;} private void putAllForCreate(Map<? extends K, ? extends V> m) {for (Map.Entry<? extends K, ? extends V> e : m.entrySet())putForCreate(e.getKey(), e.getValue());}private void putForCreate(K key, V value) {int hash = null == key ? 0 : hash(key);int i = indexFor(hash, table.length);/*** Look for preexisting entry for key. This will never happen for* clone or deserialize. It will only happen for construction if the* input Map is a sorted map whose ordering is inconsistent w/ equals.*/for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {e.value = value;return;}}createEntry(hash, key, value, i);}void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];table[bucketIndex] = new Entry<>(hash, key, value, e);size++;}Entry(int h, K k, V v, Entry<K,V> n) {value = v;next = n;key = k;hash = h;}

序列化方法

序列化方法就是將Map keySet中的元素依次寫出,然后在對端依次讀入,重建HashMap

private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// Write out any hidden serialization magics.defaultWriteObject();// Write out HashMap capacity and load factors.writeInt(map.capacity());s.writeFloat(map.loadFactor());// Write out sizes.writeInt(map.size());// Write out all elements in the proper order.for (E e : map.keySet())s.writeObject(e);}private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in any hidden serialization magics.defaultReadObject();// Read in HashMap capacity and load factor and create backing HashMapint 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 sizeint 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);}}

總結(jié)

以上是生活随笔為你收集整理的Java HashSet源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。