關(guān)于HashSet與HashMap的區(qū)別及說(shuō)明
HashSet與HashMap的區(qū)別
HashSet 集合不允許存儲(chǔ)相同的元素, 它底層實(shí)際上使用 HashMap 來(lái)存儲(chǔ)元素的,不過(guò)關(guān)注的只是key元素, 所有 value元素默認(rèn)為 Object類(lèi)對(duì)象.
HashSet源碼如下
HashSet 的構(gòu)造方法
//HashSet底層用來(lái)存儲(chǔ)元素的結(jié)構(gòu),實(shí)際上使用HashMap來(lái)存儲(chǔ) private transient HashMap<E,Object> map; //HashMap中的value值,HashSet只關(guān)注key值,所以所有的value值都為Object對(duì)象 private static final Object PRESENT = new Object(); //HashSet的無(wú)參構(gòu)造,直接創(chuàng)建了一個(gè)HashMap對(duì)象 public HashSet() { map = new HashMap<>(); } //指定初始化容量和負(fù)載因子 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //給定初始化容量 public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
可以看到 HashSet的構(gòu)造方法底層都是調(diào)用 HashMap的構(gòu)造方法, 所以HashSet底層實(shí)際上是使用 HashMap 來(lái)作為存儲(chǔ)結(jié)構(gòu).
當(dāng)使用無(wú)參構(gòu)造創(chuàng)建 HashSet對(duì)象時(shí), 其實(shí)調(diào)用了 HashMap的無(wú)參構(gòu)造創(chuàng)建了一個(gè) HashMap對(duì)象, 所以 HashSet 的初始化容量也為16, 負(fù)載因子也為 0.75.
再來(lái)看看 HashSet 的 add() 方法的實(shí)現(xiàn):
可以看到 HashSet 的 add() 方法底層實(shí)際也是調(diào)用了 HashMap 的 put() 方法, 這里的key為我們傳入的將要添加到 set集合中的元素, 而value值則為 PERSENT,其實(shí)就是上面分析的 HashSet類(lèi)中的一個(gè)靜態(tài)字段, 默認(rèn)為 Object對(duì)象.
HashSet并不關(guān)注value元素, 只使用 HashMap來(lái)存儲(chǔ) key元素, 這就使得 HashSet判斷元素相等的條件與 HashMap中 key相等的條件其實(shí)是一樣的, 兩個(gè)元素的 hashCode值相同且通過(guò)equals()方法比較返回 true.
所以HashSet應(yīng)該重寫(xiě) equals()和hashCode()方法, 兩個(gè)元素的 HashCode相同, 保證通過(guò)equals() 方法比較返回 true.
總結(jié)一下HashSet和HashMap的區(qū)別
(1)HashSet實(shí)現(xiàn)了Set接口, 僅存儲(chǔ)對(duì)象; HashMap實(shí)現(xiàn)了 Map接口, 存儲(chǔ)的是鍵值對(duì).
(2)HashSet底層其實(shí)是用HashMap實(shí)現(xiàn)存儲(chǔ)的, HashSet封裝了一系列HashMap的方法. 依靠HashMap來(lái)存儲(chǔ)元素值,(利用hashMap的key鍵進(jìn)行存儲(chǔ)), 而value值默認(rèn)為Object對(duì)象. 所以HashSet也不允許出現(xiàn)重復(fù)值, 判斷標(biāo)準(zhǔn)和HashMap判斷標(biāo)準(zhǔn)相同, 兩個(gè)元素的hashCode相等并且通過(guò)equals()方法返回true.
HashSet與HashMap的關(guān)系
HashSet作為一種最簡(jiǎn)單的java集合類(lèi),真的可以用三句話來(lái)概括一下:
第一句:存放不重復(fù)的數(shù)據(jù)。第二句:底層基于hash表實(shí)現(xiàn)。第三句:內(nèi)部基于HashMap。
這也就是說(shuō),你想要完完全全徹徹底底地把HashSet吃透,就一定要先吃透HashMap。這篇文章將帶著你從特點(diǎn)到存儲(chǔ),再到最后的實(shí)現(xiàn),從源碼角度來(lái)分析一下。
認(rèn)識(shí)
HashSet其實(shí)就是一個(gè)沒(méi)有重復(fù)數(shù)據(jù)的集合,基本用法很簡(jiǎn)單,我們直接給個(gè)例子。
以上只是列出了其最簡(jiǎn)單的用法。下面我們看看其繼承關(guān)系。
HashSet主要繼承了三個(gè)接口Serializable、Cloneable、Set,并且實(shí)現(xiàn)了抽象類(lèi)AbstractSet。
我們直接看看源碼:
學(xué)過(guò)HashMap的人應(yīng)該都知道HashMap實(shí)現(xiàn)的是Map接口,而HashSet是Set接口。
下面我們就從源碼的角度來(lái)分析一下HashSet。
源碼分析
1、參數(shù)變量
這里有個(gè)問(wèn)題,那就是既然HashSet只使用到了HashMap的key,為什么不使用null來(lái)充當(dāng)HashMap的value,而使用了PRESENT這個(gè)對(duì)象呢?
答:想要深入這個(gè)問(wèn)題,我們還需要深入到源碼中看看:
以上兩個(gè)是增刪方法,在add一個(gè)元素的時(shí)候,其實(shí)調(diào)用的就是map.put(e, PRESENT)==null,HashMap在put元素的時(shí)候會(huì)出現(xiàn)兩種情況:
情況一:put的元素是新的,那么map.put會(huì)發(fā)現(xiàn)key沒(méi)有,那么直接插入即可。return結(jié)果為true。
情況二:put的元素是舊的,那么map.put會(huì)發(fā)現(xiàn)key已有,則直接返回相應(yīng)的value,也就是PRESENT,PRESENT不等于null,return的也就是false了,表示HashSet插入失敗。如果我們這里使用null為map.put的參數(shù)呢?直接返回相應(yīng)的value,也就是null,這時(shí)候null==null是true。竟然返回了true。很明顯就是錯(cuò)誤的返回結(jié)果呀。
這其實(shí)也是去重復(fù)的原理。對(duì)于刪除方法其實(shí)也是一樣的。
2、構(gòu)造函數(shù)
HashSet提供的構(gòu)造方法很多,有5個(gè),在這里我想說(shuō)明的是每一種構(gòu)造方法,其實(shí)都是創(chuàng)建的HashMap。這也證明了我們文章開(kāi)頭提到的內(nèi)部基于HashMap。
3、其他方法
增刪方法我們已經(jīng)提到了,在這里我們主要看一下其他方法。
上面的方法還包含了遍歷元素的方式。
HashSet就是這么簡(jiǎn)單,源碼里面幾乎所有的方法都是HashMap實(shí)現(xiàn)的。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用Flyway進(jìn)行數(shù)據(jù)庫(kù)管理的操作方法
Flyway是一個(gè)開(kāi)源的數(shù)據(jù)庫(kù)版本管理工具,并且極力主張“約定大于配置”,簡(jiǎn)單、專(zhuān)注、強(qiáng)大。接下來(lái)通過(guò)本文給大家介紹SpringBoot使用Flyway進(jìn)行數(shù)據(jù)庫(kù)管理的方法,感興趣的朋友一起看看吧2021-09-09SpringBoot工程Docker多環(huán)境中使用同一個(gè)Jar包解決方案
在Docker多環(huán)境部署中,SpringBoot工程可以通過(guò)環(huán)境變量來(lái)動(dòng)態(tài)改變配置,無(wú)需重新打包,利用volume掛載或docker?cp命令,可以將配置文件直接傳入容器,提高部署效率,并保證安全性2024-09-09java8中NIO緩沖區(qū)(Buffer)的數(shù)據(jù)存儲(chǔ)詳解
在本篇文章中小編給大家分享了關(guān)于java8中NIO緩沖區(qū)(Buffer)的數(shù)據(jù)存儲(chǔ)的相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。2019-04-04Java實(shí)現(xiàn)對(duì)華北、華南、華東和華中四個(gè)區(qū)域的劃分
在Java中,通過(guò)定義枚舉類(lèi)、編寫(xiě)主程序和進(jìn)行測(cè)試,本文詳細(xì)介紹了如何劃分華北、華南、華東和華中四個(gè)區(qū)域,首先定義枚舉類(lèi)標(biāo)識(shí)區(qū)域,然后通過(guò)主程序接收用戶(hù)輸入并返回相應(yīng)區(qū)域,最后通過(guò)測(cè)試用例確保正確性,文章還介紹了甘特圖和餅狀圖的使用2024-09-09一文帶你掌握springBoot如何做到優(yōu)雅停機(jī)的
在分布式系統(tǒng)中,服務(wù)的優(yōu)雅停機(jī)(Graceful Shutdown)是確保業(yè)務(wù)連續(xù)性的重要機(jī)制,下面就跟隨小編一起來(lái)深入了解下springBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的具體方式吧2025-04-04