Java中LinkedHashSet的源碼分析
LinkedHashSet的基本介紹
LinkedHashSet 是 Java 中的一個(gè)集合類,它是 HashSet 的子類,同時(shí)也實(shí)現(xiàn)了 Set 接口。與 HashSet 不同的是,LinkedHashSet 保留了元素插入的順序,并且具有 HashSet 的快速查找特性。
LinkedHashSet 繼承了 HashSet,所以它是在 HashSet 的基礎(chǔ)上維護(hù)了元素添加順序的功能。
它的構(gòu)造方法是 LinkedHashSet()。 LinkedHashSet 是一個(gè)基于 LinkedHashMap 實(shí)現(xiàn)的有序去重集合列表。
它中的元素沒(méi)有重復(fù),有順序,并且可以存儲(chǔ) null 值。
需要注意的是,LinkedHashSet 是一個(gè)線程不安全的容器。
- LinkedHashSet是HashSet的子類
- LinkedHashSet底層是一個(gè)LinkedHashMap,底層維護(hù)了一個(gè)數(shù)組+雙向鏈表
- LinkedHashSet 根據(jù)元素的hashCode值來(lái)決定元素的存儲(chǔ)位置,同時(shí)使用鏈表維護(hù)元素的次序(圖),這使得元素看起來(lái)是以插入順序保存的。
- LinkedHashSet不允許添重復(fù)元素
LinkedHashSet源碼分析
1.在LinkedHastSet 中維護(hù)了一個(gè)hash表和雙向鏈表(LinkedHashSet有head和tail)
2.每一個(gè)節(jié)點(diǎn)有pre和next屬性,這樣可以形成雙向鏈表
3.在添加一個(gè)元素時(shí),先求hash值,在求索引.,確定該元素在hashtable的位置,然后將添加的元素加入到雙向鏈表(如果已經(jīng)存在,不添加[原則和hashset一樣])
tail.next=newElement//簡(jiǎn)單指定 newElement.pre=tail tail = newEelment;
4)這樣的話,我們遍歷LinkedHashSet也能確保插入順序和遍歷順序一致
源碼分析
因?yàn)長(zhǎng)inkedHashSet是HashSet的子類,因此在底層使用的方法,還是HashSet的方法,因?yàn)镠ashSet的底層是HashMap,所以最終走的還是HashMap的putVal方法
可以參考putVal方法,我們?cè)趯ashSet的時(shí)候已經(jīng)詳細(xì)的解釋過(guò)了
/*
對(duì)HashSet 的源碼解讀
1. 執(zhí)行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 執(zhí)行 add()
public boolean add(E e) {//e = "java"
//這里的PRESENT 是一個(gè)空對(duì)象數(shù)組,起到占位符作用
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.執(zhí)行 put() , 該方法會(huì)執(zhí)行 hash(key) 得到key對(duì)應(yīng)的hash值 算法h = key.hashCode()) ^ (h >>> 16)
根據(jù)我們傳入進(jìn)來(lái)的值,去計(jì)算hash值,在Table中存放的位置
public V put(K key, V value) {//key = "java" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
4.執(zhí)行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定義了輔助變量
//table 就是 HashMap 的一個(gè)數(shù)組,類型是 Node[]
//if 語(yǔ)句表示如果當(dāng)前table 是null, 或者 大小=0
//就會(huì)進(jìn)行第一次擴(kuò)容,到16個(gè)空間.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根據(jù)key,得到hash 去計(jì)算該key應(yīng)該存放到table表的哪個(gè)索引位置
//并把這個(gè)位置的對(duì)象,賦給 p
//(2)判斷p 是否為null
//(2.1) 如果p 為null, 表示還沒(méi)有存放元素, 就創(chuàng)建一個(gè)Node (key="java",value=PRESENT)
//(2.2) 就放在該位置 tab[i] = newNode(hash, key, value, null) 就是直接存放進(jìn)去
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一個(gè)開(kāi)發(fā)技巧提示: 在需要局部變量(輔助變量)時(shí)候,在創(chuàng)建
Node<K,V> e; K k; //
//當(dāng)進(jìn)入else的時(shí)候,就說(shuō)明我們當(dāng)前計(jì)算出來(lái)的Hash值在數(shù)組中的位置已經(jīng)存在了 ,那么就先進(jìn)行判斷
//如果當(dāng)前索引位置對(duì)應(yīng)的鏈表的第一個(gè)元素的hash值和準(zhǔn)備添加的key的hash值一樣
//并且滿足 下面兩個(gè)條件之一:
//(1) 準(zhǔn)備加入的keyhash值 和 p 指向的Node 結(jié)點(diǎn)的hash值相同,那就說(shuō)明是是同一個(gè)對(duì)象
//(2) 當(dāng)前的key對(duì)象 或者和我們傳入對(duì)象的地址相同,因?yàn)?=在判斷引用類型的時(shí)候,判斷的是地址是否相同,如果地址相同,或者他們的內(nèi)容相同
//就不能加入 如果不能加入就把p賦給e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判斷 p 是不是一顆紅黑樹(shù),
//如果是一顆紅黑樹(shù),就調(diào)用 putTreeVal , 來(lái)進(jìn)行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果上面的情況都不是的話,那么就說(shuō)明,此時(shí)這個(gè)索引對(duì)應(yīng)的位置是一個(gè)鏈表了
else {//如果table對(duì)應(yīng)索引位置,已經(jīng)是一個(gè)鏈表, 就使用for循環(huán)比較
//(1) 依次和該鏈表的每一個(gè)元素比較后,都不相同, 則加入到該鏈表的最后
// 注意在把元素添加到鏈表后,立即判斷 該鏈表是否已經(jīng)達(dá)到8個(gè)結(jié)點(diǎn)
// , 就調(diào)用 treeifyBin() 對(duì)當(dāng)前這個(gè)鏈表進(jìn)行樹(shù)化(轉(zhuǎn)成紅黑樹(shù))
// 注意,在轉(zhuǎn)成紅黑樹(shù)時(shí),要進(jìn)行判斷, 判斷條件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面條件成立,先table擴(kuò)容.
// 只有上面條件不成立時(shí),才進(jìn)行轉(zhuǎn)成紅黑樹(shù)
//(2) 依次和該鏈表的每一個(gè)元素比較過(guò)程中,如果有相同情況,就直接break
//這是一個(gè)死循環(huán),會(huì)一直進(jìn)行 比較,只有兩種情況,才會(huì)退出循環(huán)
//第一種:當(dāng)數(shù)組中的其中一條列表的長(zhǎng)度到達(dá)了7,準(zhǔn)備進(jìn)行樹(shù)化的時(shí)候
//第二種:就是發(fā)現(xiàn)我們加入的元素,在這個(gè)列表中發(fā)現(xiàn)了重復(fù)的,也會(huì)直接跳出循環(huán)
for (int binCount = 0; ; ++binCount) {
這里e=p.next 因?yàn)槲覀冊(cè)谧铋_(kāi)始上面的時(shí)候,已經(jīng)對(duì)第一個(gè)元素進(jìn)行了判斷,所以這里直接從下一個(gè)元素開(kāi)始判斷
如果下一個(gè)元素為空,那么就直接加入
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
加入完一個(gè)元素之后,馬上的進(jìn)行判斷,當(dāng)前列表的個(gè)數(shù)有幾個(gè),是否進(jìn)行樹(shù)化
if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//這里就是,在循環(huán)比較的過(guò)程中,如果發(fā)現(xiàn)有相同的內(nèi)容,那么會(huì)直接break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//這里就是讓p 指向下一個(gè)
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是我們每加入一個(gè)結(jié)點(diǎn)Node(k,v,h,next), size++
if (++size > threshold)
resize();//擴(kuò)容
afterNodeInsertion(evict);
return null;
}
*/package idea.chapter14.set_;
import java.util.LinkedHashSet;
import java.util.Set;
@SuppressWarnings({"all"})
public class LinkedHashSetSource {
public static void main(String[] args) {
//分析一下LinkedHashSet的底層機(jī)制
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("劉", 1001));
set.add(123);
System.out.println("set=" + set);
//源碼分析
//1. LinkedHashSet 加入順序和取出元素/數(shù)據(jù)的順序一致,因?yàn)長(zhǎng)inkedHashSet在底層維護(hù)了一個(gè)雙向鏈表+數(shù)組,因此可以保證數(shù)據(jù)取出的順序保持一致
//2. LinkedHashSet 底層維護(hù)的是一個(gè)LinkedHashMap(是HashMap的子類)
//3. LinkedHashSet 底層結(jié)構(gòu) (數(shù)組table+雙向鏈表)
//4. 添加第一次時(shí),直接將 數(shù)組table 擴(kuò)容到 16 ,存放的結(jié)點(diǎn)類型是 LinkedHashMap$Entry
//5. 數(shù)組是 HashMap$Node[] 存放的元素/數(shù)據(jù)是 LinkedHashMap$Entry類型
/*
//繼承關(guān)系是在內(nèi)部類完成.
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
*/
}
}
class Customer {
private String name;
private int no;
public Customer(String name, int no) {
this.name = name;
this.no = no;
}
}LinkedHashset練習(xí)
代碼演示:
在沒(méi)有重寫(xiě)equals方法和hashcode方法的時(shí)候都是可以加入進(jìn)去的,因?yàn)槎际莿?chuàng)建出來(lái)新的對(duì)象
package idea.chapter14.set_;
import java.util.LinkedHashSet;
import java.util.Objects;
/*
Car類(屬性:name,price), 如果name和price一樣,
。則認(rèn)為是相同元素,就不能添加。5min. I
*/
@SuppressWarnings({"all"})
public class LinkedHashSetExercise {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奧拓", 1000));//OK
linkedHashSet.add(new Car("奧迪", 300000));//OK
linkedHashSet.add(new Car("法拉利", 10000000));//OK
linkedHashSet.add(new Car("奧迪", 300000));//加入不了
linkedHashSet.add(new Car("保時(shí)捷", 70000000));//OK
linkedHashSet.add(new Car("奧迪", 300000));//加入不了
System.out.println(linkedHashSet);
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public String toString() {
return "\nCar{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}到此這篇關(guān)于Java中LinkedHashSet的源碼分析的文章就介紹到這了,更多相關(guān)LinkedHashSet的源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用RestTemplate訪問(wèn)https實(shí)現(xiàn)SSL請(qǐng)求操作
這篇文章主要介紹了使用RestTemplate訪問(wèn)https實(shí)現(xiàn)SSL請(qǐng)求操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java?Thread.currentThread().getName()?和?this.getName()區(qū)別詳
本文主要介紹了Thread.currentThread().getName()?和?this.getName()區(qū)別詳解,TestThread?testThread?=?new?TestThread();2022-02-02
Java數(shù)據(jù)結(jié)構(gòu)與算法之雙向鏈表、環(huán)形鏈表及約瑟夫問(wèn)題深入理解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)與算法之雙向鏈表、環(huán)形鏈表及約瑟夫問(wèn)題深入理解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
SpringMVC整合kinfe4j及問(wèn)題解決分析
這篇文章主要為大家介紹了SpringMVC整合kinfe4j及問(wèn)題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
spring Cloud微服務(wù)跨域?qū)崿F(xiàn)步驟
這篇文章主要介紹了spring Cloud微服務(wù)跨域?qū)崿F(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
java實(shí)現(xiàn)Img與PDF相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)Img與PDF相互轉(zhuǎn)換的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05

