Java中HashSet和LinkedHashSet詳解
一、HashSet介紹
HashSet是Set接口的子類,其內(nèi)部采用了HashMap作為數(shù)據(jù)存儲,HashSet其實(shí)就是在操作HashMap的key。
- HashSet是無序存儲的,不能保證元素的順序;
- HashSet并沒有進(jìn)行同步處理,因此是線程不安全的;
- HashSet可以存儲null元素,但只能存儲一個。
二、源碼解析
1、HashSet實(shí)現(xiàn)的接口
如下圖:
觀察上圖:
- AbstractSet類:該類提供了Set接口的骨架實(shí)現(xiàn),通過擴(kuò)展此類來實(shí)現(xiàn)集合的過程與通過擴(kuò)展AbstractCollection實(shí)現(xiàn)集合的過程相同,除了此類的子類中的所有方法和構(gòu)造函數(shù)都必須遵守由Set接口施加的附加約束(例如,添加方法不能允許將一個對象的多個實(shí)例添加到集合中)。
- Set接口:繼承Collection接口,添加了所有構(gòu)造函數(shù)的約定以及add,equals和hashCode方法的約定
- Serializable接口:主要用于序列化,即:能夠?qū)ο髮懭氪疟P。與之對應(yīng)的還有反序列化操作,就是將對象從磁盤中讀取出來。因此如果要進(jìn)行序列化和反序列化,ArrayList的實(shí)例對象就必須實(shí)現(xiàn)這個接口,否則在實(shí)例化的時候程序會報錯(java.io.NotSerializableException)。
- Cloneable接口:實(shí)現(xiàn)Cloneable接口的類能夠調(diào)用clone方法,如果沒有實(shí)現(xiàn)Cloneable接口就調(diào)用方法,就會拋出異常(java.lang.CloneNotSupportedException)。
2、HashSet中的變量
序列化ID
static final long serialVersionUID = -5024744406713321676L
底層使用HashMap來保存所有元素,確切說存儲在map的key中,并使用transient關(guān)鍵字修飾,防止被序列化
private transient HashMap<E,Object> map
常量,構(gòu)造一個虛擬的對象PRESENT,默認(rèn)為map的value值(HashSet中只需要用到鍵,而HashMap是key-value鍵值對,使用PRESENT作為value的默認(rèn)填充值,解決差異問題)
private static final Object PRESENT = new Object()
3、HashSet的構(gòu)造方法
(1)無參構(gòu)造
public HashSet() { map = new HashMap<>(); }
總結(jié):默認(rèn)的無參構(gòu)造,其底層會初始化一個空的HashMap,并使用默認(rèn)初始容量16和負(fù)載因子0.75。
(2)帶集合參數(shù)的構(gòu)造方法
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
總結(jié):帶集合參數(shù)的構(gòu)造方法,底層使用默認(rèn)的負(fù)載因子0.75和足以包含指定集合中所有怨怒是的初始容量來構(gòu)造一個HashMap。
(3)帶初始容量的構(gòu)造方法
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
總結(jié):以指定初始容量的HashMap構(gòu)造一個HashSet。
(4) 帶初始容量和負(fù)載因子的構(gòu)造方法
public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); }
總結(jié):構(gòu)造一個具有初始容量和負(fù)載因子的HashMap。
4、常用方法分析
(1)add()方法
add方法底層實(shí)際上是將該元素作為key添加到HashMap中,而PRESENT是作為默認(rèn)的map的value值。
如果添加的元素不存在,則加入到map中,并返回true。如果該元素已經(jīng)存在,則返回false。
map的put方法在添加key-value對時,如果新放入HashMap的Entry中key與集合中原有的Entry的key相同,則新添加的Entry的value會覆蓋原來Entry的value,但是key不會改變。
因此,如果向HashSet中添加一個已經(jīng)存在的元素時,新添加的集合元素不會被放入HashMap中,原有的元素也不會有任何改變,因此Set中的元素也就不會重復(fù)了
public boolean add(E e) { return map.put(e, PRESENT)==null; }
(2)remove()方法
如果給定的元素在HashSet中,則將其移除,其底層調(diào)用HashMap的remove()方法刪除指定Entry。
public boolean remove(Object o) { return map.remove(o)==PRESENT; }
(3) size()方法
返回HashSet中元素的個數(shù)
public int size() { return map.size(); }
(4)isEmpty()方法
判斷HashSet是否為空
public boolean isEmpty() { return map.isEmpty(); }
(5)contains()方法
如果HashSet中包含指定元素,則返回true。否則返回false
public boolean contains(Object o) { return map.containsKey(o); }
5、HashSet與LinkedHashSet
在HashSet的源碼中有這樣一個構(gòu)造函數(shù):
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
可以看到該構(gòu)造函數(shù)為包訪問權(quán)限,不對外公開。該構(gòu)造函數(shù)以指定的初始容量和負(fù)載因子構(gòu)造一個新的空鏈表哈希集合,其實(shí)它是對LinkedHashSet的支持。
我們可以看看LinkedHashSet的繼承關(guān)系:
因此,在這里我們可以簡單的對LinkedHashSet進(jìn)行分析,它在實(shí)現(xiàn)了Set接口、Cloneable接口和Serializable接口的同時,也繼承了HashSet。
LinkedHashSet的特點(diǎn)如下:
- LinkedHashSet 是 HashSet 的子類
- LinkedHashSet 底層是一個 LinkedHashMap,底層維護(hù)了一個數(shù)組+雙向鏈表
- LinkedHashSet 根據(jù)元素的 hashCode 值來決定元素的存儲位置,同時使用鏈表維護(hù)元素的次序,這使得元素看起來是以插入順序保存的。
- LinkedHashSet 不允許添重復(fù)元素
來看看LinkedHashSet的構(gòu)造方法
//使用指定的初始容量和負(fù)載因子構(gòu)造一個新的哈希集合 public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } //使用指定的初始容量和負(fù)載因子構(gòu)造新的哈希集合 public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } //無參構(gòu)造方法,默認(rèn)容量16,負(fù)載因子0.75 public LinkedHashSet() { super(16, .75f, true); } //基于集合構(gòu)造一個帶有指定元素的非空哈希集合 public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); }
LinkedHashSet維護(hù)了一個hash表和雙向鏈表,且通過head和tail分別指向鏈表的頭和尾,每一個節(jié)點(diǎn)有before和after屬性,這樣可以形成雙向鏈表。
在添加一個元素時,先求hash值,再求索引,確定該元素在table表中的位置,然后將添加的元素加入到雙向鏈表(如果該元素已經(jīng)存在,則不添加)
三、總結(jié)
1、HashSet總結(jié)
(1)基于HashMap實(shí)現(xiàn)的,默認(rèn)構(gòu)造函數(shù)是構(gòu)建一個初始容量為16,負(fù)載因子為0.75 的HashMap。封裝了一個 HashMap 對象來存儲所有的集合元素,所有放入 HashSet 中的集合元素實(shí)際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個 PRESENT,它是一個靜態(tài)的 Object 對象。
(2)當(dāng)我們試圖把某個類的對象當(dāng)成 HashMap的 key,或試圖將這個類的對象放入 HashSet 中保存時,重寫該類的equals(Object obj)方法和 hashCode() 方法很重要,而且這兩個方法的返回值必須保持一致:當(dāng)該類的兩個的 hashCode() 返回值相同時,它們通過 equals() 方法比較也應(yīng)該返回 true。通常來說,所有參與計算 hashCode() 返回值的關(guān)鍵屬性,都應(yīng)該用于作為 equals() 比較的標(biāo)準(zhǔn)。
(3)HashSet的其他操作都是基于HashMap的。
2、LinkedHashSet總結(jié)
LinkedHashSet 底層使用 LinkedHashMap 來保存所有元素,它繼承于 HashSet,其所有的方法操作上又與 HashSet 相同,因此 LinkedHashSet 的實(shí)現(xiàn)上非常簡單,只提供了四個構(gòu)造方法,并通過傳遞一個標(biāo)識參數(shù),調(diào)用父類的構(gòu)造器,底層構(gòu)造一個 LinkedHashMap 來實(shí)現(xiàn),在相關(guān)操作上與父類 HashSet 的操作相同,直接調(diào)用父類 HashSet 的方法即可。
到此這篇關(guān)于Java中HashSet和LinkedHashSet詳解的文章就介紹到這了,更多相關(guān)HashSet和LinkedHashSet內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合分頁插件PageHelper步驟解析
這篇文章主要介紹了Springboot整合分頁插件PageHelper步驟解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06關(guān)于Java中的try-with-resources語句
這篇文章主要介紹了關(guān)于Java中的try-with-resources語句,try-with-resources是Java中的環(huán)繞語句之一,旨在減輕開發(fā)人員釋放try塊中使用的資源的義務(wù),需要的朋友可以參考下2023-05-05一文徹底弄懂Java中MultipartFile接口和File類
MultipartFile是一個接口,我們可以理解為是Spring?給我們綁定的一個在使用文件上傳等時簡便實(shí)現(xiàn)的口子,這篇文章主要給大家介紹了關(guān)于如何通過一文徹底弄懂Java中MultipartFile接口和File類的相關(guān)資料,需要的朋友可以參考下2023-11-11Java基于面向?qū)ο髮?shí)現(xiàn)一個戰(zhàn)士小游戲
這篇文章主要為大家詳細(xì)介紹了Java如何基于面向?qū)ο髮?shí)現(xiàn)一個戰(zhàn)士小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動手嘗試一下2022-07-07java 四舍五入保留小數(shù)的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猨ava 四舍五入保留小數(shù)的實(shí)現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09Java利用Request請求如何獲取IP地址對應(yīng)的省份、城市詳解
之前已經(jīng)給大家介紹了關(guān)于Java用Request請求獲取IP地址的相關(guān)內(nèi)容,那么下面這篇文章將給大家進(jìn)入深入的介紹,關(guān)于Java利用Request請求如何獲取IP地址對應(yīng)省份、城市的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10