【從0開始學習Java | 第17篇】集合(中-Set部分)

在這里插入圖片描述

文章目錄

  • Java集合之Set:無序不重復的元素容器
    • 一、Set接口的核心特性
    • 二、常用實現類及底層原理
      • 1. HashSet:基于哈希表的高效實現
      • 2. LinkedHashSet:保留插入順序的哈希實現
      • 3. TreeSet:基于紅黑樹的排序實現
    • 三、實現類對比與選擇建議
    • 四、使用注意事項
    • 總結

Java集合之Set:無序不重復的元素容器

在Java集合框架中,Set是與List并列的重要接口,它以“無序且元素不重復”為核心特性,在數據去重、快速查找等場景中被廣泛應用。本文將深入解析Set接口及其常用實現類,幫助你理解它們的底層原理與適用場景。

一、Set接口的核心特性

Set繼承自Collection接口,但其行為與List有顯著區別:

  • 無序性:元素的存儲順序與插入順序無關(特殊實現類如LinkedHashSet除外),不能通過索引訪問元素。
  • 唯一性/不重復:集合中不允許存在重復元素,判斷重復的依據是equals()方法,若元素重寫了equals(),通常需同時重寫hashCode()以保證一致性。
  • 無索引:沒有類似get(int index)的方法,遍歷需通過迭代器(Iterator)或增強for循環。

Set接口的常用方法與Collection基本一致,如add()remove()contains()size()等,核心差異體現在實現類對“去重”和“查找效率”的不同優化上。

在這里插入圖片描述

二、常用實現類及底層原理

1. HashSet:基于哈希表的高效實現

在這里插入圖片描述

在這里插入圖片描述1. JDK8以后,當鏈表長度超過8,而且數組長度大于等于64時,自動轉換為紅黑樹
2. 如果集合中存儲的是自定義對象,必須重寫hashCodeequals方法才能用來比較屬性值是否相同

在知道了HashSet的底層原理后,我們就可以回答以下三個問題了:

1. HashSet為什么存和取的順序不一樣?

因為HashSet可能是數組+鏈表+紅黑樹的結合體,而取的時候,是從最小的地址值開始遍歷,而先放進去的元素的地址值不一定就小,以上都造成了HashSet的存取順序不同。

2. HashSet為什么沒有索引?

因為HashSet是數組+鏈表+紅黑樹的結合體,若存在索引,無法分配索引,難道同一個鏈表上和同一個紅黑樹上的元素都用同一個索引碼,因此便取消了HashSet的索引。

3. HashSet是利用什么機制保證數據去重的?

首先利用 HashCode方法來確定元素的位置,再通過equals方法來判斷屬性值是否重復【重寫了HashCode 和 equals 方法】。

HashSetSet最常用的實現類,底層通過哈希表(HashMap) 實現,其核心特性如下:

  • 存儲原理:借助HashMapkey存儲元素(value為固定常量),利用哈希表的特性保證元素唯一。
  • 去重邏輯:添加元素時,先通過hashCode()計算哈希值,若哈希值不同則直接存儲;若哈希值相同,再通過equals()判斷是否為同一元素,相同則拒絕添加。
  • 性能
    • add()、remove()、contains()等操作的平均時間復雜度為O(1),效率極高。
    • 無序性:元素存儲位置由哈希值決定,與插入順序無關。
  • 注意事項
    • 元素需重寫hashCode()equals(),否則可能導致重復元素存入(如自定義對象未重寫方法時,默認使用地址值判斷【重要的事反復強調】)。
    • 線程不安全,多線程環境需手動同步(如使用Collections.synchronizedSet())。

示例代碼

Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("apple"); // 重復元素,添加失敗
System.out.println(hashSet); // 輸出可能為 [banana, apple](順序不確定)

2. LinkedHashSet:保留插入順序的哈希實現

在這里插入圖片描述

LinkedHashSet繼承自HashSet,底層通過LinkedHashMap實現,在哈希表基礎上增加了雙向鏈表,用于記錄元素的插入順序,因此具備以下特性:

  • 有序性:遍歷元素時按插入順序返回,解決了HashSet的無序問題。
  • 性能
    • 插入和刪除效率略低于HashSet(因需維護鏈表),但遍歷效率更高。
    • 時間復雜度仍為O(1)(平均),與HashSet接近。
  • 適用場景:需要去重且保留插入順序的場景(如日志記錄、歷史操作跟蹤)。

示例代碼

Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("first");
linkedHashSet.add("second");
linkedHashSet.add("first"); // 去重
System.out.println(linkedHashSet); // 輸出 [first, second](順序與插入一致)

3. TreeSet:基于紅黑樹的排序實現

TreeSet底層通過紅黑樹(自平衡二叉查找樹) 實現,核心特性是元素可排序

  • 兩種排序方式
    • 自然排序:元素實現Comparable接口,重寫compareTo()方法(如IntegerString默認支持)。

代碼示例:

public class Student implements Comparable<Student> {private String name;private int age;@Overridepublic int compareTo(Student o) {//this:表示當前要添加的元素//o:表示已經在紅黑樹存在的元素return this.age - o.age; // 按年齡升序排列}
}

排序規則圖示:

在這里插入圖片描述

適用場景

  • 元素類有明確且唯一的默認排序邏輯(如數字按大小、字符串按字典序)。
  • 排序規則需要在多處復用,且不會頻繁變更。
  • 元素類是自定義類,允許修改其代碼以實現 Comparable 接口。

定制排序 / 比較器排序:創建TreeSet時傳入Comparator對象,自定義排序邏輯。

// 按字符串長度排序
// lambda表達式版:
TreeSet<String> ts= new TreeSet<>((o1, o2) -> o1.length() - o2.length());// 原始版本:
TreeSet<String> ts= new TreeSet<>(new Comparator<String>() {@Override// o1:表示當前要添加的元素// o2:表示已經在紅黑樹存在的元素public int compare(String o1, String o2) {return o1.length() - o2.length();}
});

適用場景

  • 元素類無法修改(如 String、Integer 等 JDK 內置類),無法實現 Comparable
  • 需要臨時改變排序規則(同一批元素在不同場景下按不同規則排序)。
  • 元素類已有默認排序,但需要覆蓋默認規則(如數字默認升序,需臨時按降序排列)。

  • 去重邏輯:通過排序規則判斷元素是否重復(compareTo()返回0則視為重復,而非 equals() 方法,因為其底層為紅黑樹,非哈希表)。
  • 性能
    • add()、remove()、contains()操作的時間復雜度為O(log n),適合需要頻繁排序和查找的場景。
    • 遍歷元素時按排序順序返回,無需額外排序操作。
  • 注意事項
    • 元素必須可比較(否則會拋出ClassCastException)。
    • 線程不安全,多線程環境需同步處理。

示例代碼

// 自然排序(String默認按字典序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("orange");
treeSet.add("apple");
treeSet.add("banana");
System.out.println(treeSet); // 輸出 [apple, banana, orange](按字典序排序)// 定制排序(整數降序)
Set<Integer> customTreeSet = new TreeSet<>((a, b) -> b - a);
customTreeSet.add(3);
customTreeSet.add(1);
customTreeSet.add(2);
System.out.println(customTreeSet); // 輸出 [3, 2, 1]

使用原則默認使用第一種,如果第一種不能滿足當前需求,就使用第二種

三、實現類對比與選擇建議

三者均不重復,且無索引 ? 哈希表 => 數組+單向鏈表+紅黑樹

實現類底層結構有序性去重依據平均時間復雜度適用場景
HashSet哈希表無序hashCode() + equals()O(1)高效去重,不關心順序
LinkedHashSet哈希表+雙向鏈表有序,插入順序hashCode() + equals()O(1)去重且需保留插入順序
TreeSet紅黑樹可排序compareTo()/ComparatorO(log n)去重且需排序或頻繁范圍查詢

四、使用注意事項

  1. 元素的不可變性:若元素是可變對象,修改其屬性可能導致哈希值或排序位置變化,引發Set內部狀態混亂(如HashSet中元素修改后可能無法被正確查找)。建議使用不可變對象(如StringInteger)作為Set元素。

  2. hashCode()與equals()的一致性

    • a.equals(b) == true,則a.hashCode()必須等于b.hashCode()
    • 重寫時盡量保證哈希值分布均勻(減少哈希沖突),避免影響HashSet性能。
  3. 線程安全:所有Set實現類均線程不安全,多線程并發修改時需使用Collections.synchronizedSet()包裝,或直接使用ConcurrentHashMap(JDK 1.8+)的newKeySet()方法創建線程安全的Set

總結

HashSet追求高效,LinkedHashSet兼顧順序,TreeSet專注排序。理解它們的底層原理(哈希表、紅黑樹)和特性差異,能幫助你在實際開發中選擇最合適的容器,提升代碼效率與可讀性。


如果我的內容對你有幫助,請 點贊 評論 收藏 。創作不易,大家的支持就是我堅持下去的動力!
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98770.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98770.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

玩轉Docker | 使用Docker部署dufs文件管理工具

玩轉Docker | 使用Docker部署dufs文件管理工具 前言 一、 dufs介紹 Dufs簡介 核心特性 ?? 靜態文件服務 ?? 文件夾打包下載 ?? 拖拽上傳文件/文件夾 ?? 文件在線創建、編輯與搜索 ? 斷點續傳與部分傳輸 ?? 細粒度訪問控制 ?? HTTPS 安全傳輸 ?? WebDAV 兼容支持…

【混合開發】vue+Android、iPhone、鴻蒙、win、macOS、Linux之android 把assert里的dist.zip 包解壓到sd卡里

一圖勝千言 上一篇有 <!-- 讀寫外部存儲 --> <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion"28"/> <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE&qu…

線程的創建.銷毀

線程線程的創建在 C 中&#xff0c;線程的創建核心是通過std::thread類實現的&#xff0c;其構造函數需要傳入一個可調用對象&#xff08;Callable Object&#xff09;作為線程入口。可調用對象包括普通函數、lambda 表達式、函數對象&#xff08;functor&#xff09;、類的成員…

MySQL基礎全面解析

MySQL作為最流行的關系型數據庫管理系統之一&#xff0c;是每一位開發者必備的核心技能。本文將系統性地解析MySQL的基礎知識&#xff0c;結合關鍵概念與實戰應用&#xff0c;幫助您構建扎實的數據庫基礎。1. SQL與NoSQL的本質區別SQL&#xff08;結構化查詢語言&#xff09;數…

4、幽絡源微服務項目實戰:后端公共模塊創建與引入多租戶模塊

前言 上節我們將電網巡檢系統的前端vue2項目創建、配置&#xff0c;并構建了最基礎的多租戶界面&#xff0c;本節來繼續構建后端的公共模塊、多租戶模塊&#xff0c;并將公共模塊引入到多租戶模塊中。 創建公共模塊和多租戶模塊 在back父工程下創建兩個Module&#xff0c;和…

STM32學習路線開啟篇:芯片簡介與課程簡介

編寫不易,請多多指教,覺得不錯可以關注一下,相互學習 前言 一、課程配套資源 1、面包板 2、面包板專用的跳線 3、面包板的飛線 4、杜邦線 5、STM32F103C8T6最小系統板 6、0.96寸的OLED顯示屏模塊 7、電位器 8、按鈕 9、LED燈 10、STLINK 11、USB轉串口(TTL)模塊 12、源蜂鳴器模…

圖像直方圖

圖像直方圖就是用來統計圖像像素值分布的。灰度圖分布讀取灰度圖phone cv2.imread(phone.png, cv2.IMREAD_GRAYSCALE) a phone.ravel() plt.hist(a, bins256) plt.show()如何可以獲得當前像素值分布讀取各通道的像素值分布img cv2.imread(phone.png) colors (b, g, r) for …

分類別柱狀圖(Vue3)

效果圖&#xff1a;需求&#xff1a;男女年齡段占比<template><div class"go-ClassifyBar01"><v-chartref"vChartRef":option"option"style"width: 100%; height: 800px"></v-chart></div> </templa…

Apache Dubbo學習筆記-使用Dubbo發布、調用服務

Apache Dubbo經常作為一個RPC框架來使用&#xff0c;這篇文章主要介紹使用Dubbo配合注冊中心來發布和調用服務。 Apache Dubbo和Spring Boot、JDK的版本對應關系。 Dubbo 分支最新版本JDKSpring Boot組件版本詳細說明3.3.x (當前文檔)3.3.08, 17, 212.x、3.x詳情- 版本變更記錄…

Python學習——字典和文件

前面python的學習中我們已經學習了python的函數和列表元組相關的內容&#xff0c;接下來我們來學習剩下的python語法&#xff1a;字典和文件 相關代碼已經上傳到作者的個人gitee&#xff1a;樓田莉子/Python 學習喜歡請點個贊謝謝 目錄 字典 創建字典 查找key 新增/修改元素 …

swiper插件的使用

官方網址&#xff1a;https://www.swiper.com.cn/ 1、點擊導航欄&#xff0c;獲取Swiper里邊的下載Swiper 2、選擇要下載的版本【本次案例版本5.4.5】&#xff0c;然后解壓縮文件夾&#xff0c;拿到swiper.min.js和swiper.min.css文件&#xff0c;放到項目對應的css文件和js文…

Vue3+JS 組合式 API 實戰:從項目痛點到通用 Hook 封裝

Vue3 組合式 API 的實戰技巧 —— 組合式 API 幫我解決了不少 Options API 難以應對的問題&#xff0c;尤其是在代碼復用和復雜組件維護上。一、為什么放棄 Options API&#xff1f;聊聊三年項目里的真實痛點?剛接觸 Vue3 時&#xff0c;我曾因 “慣性” 繼續用 Options API 寫…

把 AI 塞進「電梯按鈕」——基于 64 kB 零樣本聲紋的離線故障預測按鈕

標簽&#xff1a;零樣本聲紋、電梯按鈕、離線 AI、TinyML、RISC-V、低功耗、GD32V303、故障預警 ---- 1. 背景&#xff1a;為什么按鈕要「聽聲音」&#xff1f; 全國 700 萬臺電梯&#xff0c;按鈕故障率 0.3 %/年&#xff0c;卻常出現&#xff1a; ? 機械卡滯、觸點氧化&…

清華大學聯合項目 論文解讀 | MoTo賦能雙臂機器人:實現零樣本移動操作

研究背景 移動操作是機器人領域的核心挑戰&#xff0c;它使機器人能夠在各種任務和動態日常環境中為人類提供幫助。傳統的移動操作方法由于缺乏大規模訓練&#xff0c;往往難以在不同任務和環境中實現泛化。而現有操作基礎模型雖在固定基座任務中表現出強泛化性&#xff0c;卻無…

go webrtc - 2 webrtc重要概念

webrtc是一套音視頻傳輸技術生態&#xff0c;不是一個協議或一個什么東西。3種模式本文基于 SFU 形式闡述&#xff01;重要概念&#xff1a;sfu 服務負責&#xff1a;信令 服務負責&#xff1a;peerConnection&#xff1a;track&#xff1a;房間&#xff1a;虛擬分組概念用戶&a…

“下游任務”概念詳解:從定義到應用場景

“下游任務”概念詳解&#xff1a;從定義到應用場景 一、什么是“下游任務”&#xff1f; 在機器學習&#xff08;尤其是深度學習&#xff09;中&#xff0c;“下游任務”&#xff08;Downstream Task&#xff09;是相對“上游過程”而言的目標任務——可以理解為&#xff1a;我…

視頻怎么做成 GIF?用 oCam 一鍵錄制 GIF 動畫超簡單

GIF 動圖因其生動直觀、無需點擊播放的特點&#xff0c;越來越受歡迎。你是否也曾看到一段有趣的視頻&#xff0c;想把它做成 GIF 發給朋友或用在PPT里&#xff1f;其實&#xff0c;將視頻片段轉換為 GIF 并不需要復雜的視頻剪輯技術&#xff0c;使用一款支持直接錄制為 GIF 的…

Vue.config.js中的Webpack配置、優化及多頁面應用開發

Vue.config.js中的Webpack配置、優化及多頁面應用開發 在Vue CLI 3項目中&#xff0c;vue.config.js文件是工程化配置的核心入口&#xff0c;它通過集成Webpack配置、優化策略和多頁面開發支持&#xff0c;為項目構建提供高度可定制化的解決方案。本文將從基礎配置、性能優化、…

行業學習【電商】:直播電商的去頭部化、矩陣號?

聲明&#xff1a;以下部分內容含AI生成這兩個詞是當前直播電商和MCN領域的核心戰略&#xff0c;理解了它們就理解了行業正在發生的深刻變化。一、如何理解“去頭部化”&#xff1f;“去頭部化” 指的是平臺或MCN機構有意識地減少對超頭部主播&#xff08;如曾經的李佳琦、薇婭&…

【MFC視圖和窗口基礎:文檔/視圖的“雙胞胎”魔法 + 單文檔程序】

大家好&#xff0c;我是你的MFC編程小伙伴&#xff01;學MFC就像探險古墓&#xff1a;到處是神秘的“房間”&#xff08;窗口&#xff09;和“寶藏”&#xff08;數據&#xff09;。今天咱們聊聊核心概念 – 視圖、窗口和文檔。這些是MFC的“骨架”&#xff0c;懂了它們&#x…