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