Java筆記(16) Collection集合-->Set集合-->HashSet

来源:https://www.cnblogs.com/hiibird/archive/2023/04/15/17321589.html
-Advertisement-
Play Games

1. Set介面基本介紹 Set是無序集合(添加和取出的順序不一致,但取出的順序是固定的),沒有索引 不允許重覆元素,所以最多包含一個null JDK API中Set介面的實現類有: Abstract, ConcurrentHashMap.KeySetView, ConcurrentSkipList ...


1. Set介面基本介紹

  1. Set是無序集合(添加和取出的順序不一致,但取出的順序是固定的),沒有索引
  2. 不允許重覆元素,所以最多包含一個null
  3. JDK API中Set介面的實現類有:
    Abstract, ConcurrentHashMap.KeySetView, ConcurrentSkipListSet, CopyOnWriteArraySet, EnumSet, HashSet, JobStateReasons, LinkedHashSet, TreeSet

1.1 Set介面的常用方法

Set介面和List介面一樣,都是Collection的子介面,因此常用方法和Collection介面一樣

1.2 Set介面的遍歷方法

同Collection的遍歷方式一樣,因為Set介面是Collection介面的子介面。

  1. 可以使用迭代器
  2. 增強for迴圈
  3. 不能使用索引的方式來獲取

2 HashSet

2.1 HashSet的全面說明

  1. HashSet實現了Set介面,類定義如下:
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
  1. HashSet實際上是HashMap,HashSet的無參構造函數如下:
public HashSet() {
    map = new HashMap<>();
}
  1. 可以存放null值,但因為不能重覆,所以只能存放一個null
  2. HashSet不保證元素是有序的,取決於hash之後,在確定索引的結果,也因此取出順序是固定的
  3. 不能有重覆元素/對象。

2.2 有關重覆元素的經典問題

//定義一個類
class Dog{
    private String name;
    public Dog(String name){
        this.name = name;
    }
}
//定義一個HashSet
Set<Object> set = new HashSet<>();
set.add(new Dog("dog"));
set.add(new Dog("dog"));
System.out.println(set);

添加兩個Dog對象
兩個dog都能添加成功!前面不是說不能有重覆元素嗎?事實上,HashSet判斷元素是否重覆依靠的是HashCode,而上面的代碼並沒有重寫HashCode和equals方法,導致HashSet在判斷兩個Dog對象是否重覆時,是以地址為依據判斷的,而兩個對象實例其在堆上的記憶體必然是不一樣的,因此他們兩個被認為是不同的實例。
相同的問題使用String再來驗證一下:

set.add("john");
set.add("john");
System.out.println(set);

添加兩個String對象
毫無疑問地添加失敗了,這是因為"john"被放在了常量池中,地址不變了嗎?

set.add(new String("john"));
set.add(new String("john"));
System.out.println(set);


結果仍然添加失敗,這兩個String對象的記憶體地址不同,卻仍被準確識別為重覆元素,是因為String類重寫了HashCode和equals方法,HashSet在判斷過程中比較的是二者的內容是否一致,而不再是地址了

2.3 HashSet底層機制說明

HashSet底層是HashMap,HashMap底層是(數組+鏈表+紅黑樹)
HashSet添加元素的操作(hash()+equals()):

  1. HashSet底層是HashMap
  2. 添加一個元素是,先得到hash值,會轉成索引值
  3. 找到存儲數據表table,看這個索引位置是否已經存放的有元素
  4. 如果沒有,直接加入
  5. 如果有,調用equals比較內容,如果相同,就放棄添加,如果不相同,則添加到最後,形成鏈表
  6. 在Java8中,如果一條鏈表的元素個數超過TREEIFY_THRESHOLD(預設是8),並且table的大小 >= MIN_TRESHOLD_CAPACITY(預設是64),就會進行樹化(紅黑樹)
  7. HashSet底層的HashMap,第一次添加時,table數組擴容到16,臨界值(threshold)是16*0.75(載入因數, loadFactor) = 12;
  8. 如果table數組使用到了臨界值12,或者某條單鏈長度超過8,就會擴容到16*2 = 32,新的臨界值就是32*0.75 = 24。以此類推
  9. 在Java8中,如果一條鏈表的元素個數達到TREE_THRESHOLD(預設是8),並且table的大小>=MIN_TREEIFY_CAPACITY(預設是64),就會進行樹化(紅黑樹),否則仍然採用數組擴容機制
  10. 臨界值比較的是table中的所有節點個數,不論這個節點是直接存儲在table中,還是附加在某一條鏈表後
  11. 如果table沒有達到64,而單鏈長度超過8,會立即觸發擴容,並且每次檢測到超長都會觸發一次擴容,即使沒有達到threshold,直到table長度達到64後,觸發樹化

2.4 set.add()調用過程

    HashSet<Object> set = new HashSet<>();
    set.add("john");

以上代碼的調用過程如下圖:

2.4 set.add()調用過程

    HashSet<Object> set = new HashSet<>();
    set.add("john");

以上代碼的調用過程如下圖:
set.add()調用過程
圖中標註了調用順序和返回順序
其中,最關鍵的方法:final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)詳細分解如下:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        //定義了一些輔助變數,table就是HashMap的一個數組,類型是Node[]
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //if語句表示如果當前table是null,或者length==0,就第一次擴容到16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //(1) 根據key得到Hash,去計算該key應該存放到table的那個索引位置,並把這個位置的對象賦給p
        //(2) 判斷p是否為null
        //(2.1) 如果p為null,表示該位置還沒有存放過元素,即沒有發生哈希衝突,就創建一個Node(key="java",value=PRESENT),
        //      就放在該位置 tab[i] = new Node(hash,key,value,null);
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

            //(2.2) 如果p不為null,表示該位置已經存放過元素,即發生了哈希碰撞,
        else {
            //定義了一些輔助變數。一個開發技巧的提示:在需要的局部變數(輔助變數)時再創建
            Node<K,V> e; K k;
            //(2.2.1) 如果當前索引位置對應的鏈表的第一個元素和準備添加的key的hash值一樣,並且滿足下麵兩個條件之一,就認為傳入了重覆元素,不能加入
            // 條件一: 準備加入的key和 p指向的Node節點的key是同一個對象
            // 條件二: 調用equals()方法比較二者,結果為ture,即認為他們內容相同,註意,這裡的equals()方法是程式員定義的,不是單純的比較內容
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //再判斷p是不是一棵紅黑樹,如果是一顆紅黑樹,就調用putTreeVal()來添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

            else {
                //如果table對應索引位置已經是一個鏈表,就用for迴圈比較
                for (int binCount = 0; ; ++binCount) {
                    //(1) 依次和鏈表的每一個元素比較後,都不相同,將該元素添加至該鏈表最後
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //在把元素添加到鏈表後,立即判斷該鏈表長度是否超過8個節點,如果是,就嘗試將該鏈表轉化為紅黑樹
                        //在進行樹化時,還有一層判斷:if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize();
                        //如果if條件成立,就會先對table擴容;如果不成立,在轉化成紅黑樹
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //(2) 如果比較過程中發現重覆元素,退出返回
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    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;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);//該方法是HashMap留給子類實現的方法,對於HashMap來說,是一個空方法
        return null;//返回null代表成功,否則會在前面的return語句中返回當前索引指向的對象
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.背景描述 2020年團隊決定對elasticsearch升級。es(elasticsearch縮寫,下同)當前版本為0.9x,升級到5.x版本。es在本公司承載三個部分的業務,站內查詢,訂單數據統計,elk日誌分析。 對於站內查詢和訂單數據統計,當前業務架構是 mysql -> canal -> ...
  • Mysql 中,為什麼 WHERE 使用別名會報錯,而 ORDER BY 不會報錯? 我們先對salary * 12 命名一個別名annual_sal SELECT employee_id,salary,salary * 12 annual_sal FROM employees ORDER BY a ...
  • 1.車系頁佈局渲染現狀 車系頁是重要的車系信息頁面,更新迭代多年,頁面佈局不斷變化,xml佈局文件越寫越複雜。 獲取車系頁佈局文件耗時: startTime = System.currentTimeMillis(); setContentView(R.layout.car_series_revisi ...
  • “我苦心鍛煉了三年,我變禿了,也變強了。” —— 琦玉老師 0x00 大綱 0x01 前言 四個月前,我在《你是來找茬的吧?對自己的博客進行調優》一文中探討了以博客的使用者而不是開發者身份去進行優化,究竟能做到何種程度的問題。當時以 Edge 瀏覽器的開發者工具里的 lighthouse 評分和載入 ...
  • #例子 星巴茲是以擴張速度最快而聞名的咖啡連鎖店。因為擴張速度實在太快,他們著急更新訂單系統,來匹配他們的飲料供應要求。 ##實現1 繼承 購買咖啡時,也可以要求其中加入各種調料,例如:蒸奶,豆漿 很明顯,星巴茲為自己製造了一個維護噩夢,如果牛奶的價錢上揚,怎麼辦?新增一種焦糖調料風味時,怎麼辦 調 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. 使用方式 在 VS 中使用 VLD 的方法可以查看另外一篇博客:在 VS 2015 中使用 VLD。 2. 輸出報告 在 VS 中使用 VLD 時的輸出報告,與在 QT 中使用時是一致的 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹在 VS 2015 中使用 VLD。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. 使用前的準備 參考本人另一篇博客 安裝 Visual Leak Detector 下載 vld-2.5.1-setup.exe 並按步驟安裝 ...
  • 標記介面 標記介面(Marker Interface),又稱標簽介面(Tag Interface) 僅代表一個標記 不包含任何方法 標記介面是用來判斷某個類是否具有某種能力 Cloneable標記介面 此類實現了 Cloneable 介面,以指示 Object.clone 方法可以合法地對該類實例進 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...