Java集合框架之Set的實(shí)現(xiàn)類(lèi)與實(shí)戰(zhàn)使用詳解
一、章節(jié)學(xué)習(xí)目標(biāo)與重點(diǎn)
1.1 學(xué)習(xí)目標(biāo)
- 深入理解 Set 接口的核心特性與設(shè)計(jì)原則,明確其與 List 接口的本質(zhì)區(qū)別
- 全面掌握 HashSet、LinkedHashSet、TreeSet 三大核心實(shí)現(xiàn)類(lèi)的底層數(shù)據(jù)結(jié)構(gòu)與工作原理
- 精通各 Set 實(shí)現(xiàn)類(lèi)的核心方法源碼邏輯,理解其去重機(jī)制、排序規(guī)則與性能差異
- 能夠根據(jù)業(yè)務(wù)場(chǎng)景(去重、有序、排序、并發(fā)等)精準(zhǔn)選擇合適的 Set 實(shí)現(xiàn)類(lèi)
- 熟練解決 Set 使用過(guò)程中的常見(jiàn)問(wèn)題(如去重失效、排序異常、性能瓶頸等)
1.2 學(xué)習(xí)重點(diǎn)
- HashSet 的哈希表底層實(shí)現(xiàn)與去重機(jī)制(hashCode() 與 equals() 方法的協(xié)同作用)
- LinkedHashSet 雙向鏈表與哈希表的結(jié)合設(shè)計(jì),以及有序性保障原理
- TreeSet 的紅黑樹(shù)結(jié)構(gòu)與自然排序/定制排序?qū)崿F(xiàn)
- 三大 Set 實(shí)現(xiàn)類(lèi)的性能對(duì)比與適用場(chǎng)景選型
- Set 與 List、Map 集合的關(guān)聯(lián)關(guān)系及轉(zhuǎn)換技巧
二、Set 接口核心特性與設(shè)計(jì)理念
?? Set 作為 Java 集合框架的核心接口之一,繼承自 Collection 接口,其最核心的設(shè)計(jì)理念是元素不可重復(fù)性和無(wú)序性(部分實(shí)現(xiàn)類(lèi)支持有序):
- 不可重復(fù)性:Set 中不允許存儲(chǔ)兩個(gè)相等的元素,即對(duì)于任意兩個(gè)元素 e1 和 e2,若 e1.equals(e2) 為 true,則 e1 和 e2 不能同時(shí)存在于 Set 中
- 無(wú)序性:元素的插入順序與遍歷順序不一定一致(HashSet 完全無(wú)序,LinkedHashSet 保持插入順序,TreeSet 保持排序順序)
- 核心方法:Set 接口的方法與 Collection 接口基本一致,未新增專(zhuān)屬方法,其核心差異體現(xiàn)在實(shí)現(xiàn)類(lèi)的底層邏輯(去重、排序等)
// Set 接口核心方法(繼承自 Collection)
public interface Set<E> extends Collection<E> {
// 添加元素(若元素已存在則返回 false)
boolean add(E e);
// 批量添加元素
boolean addAll(Collection<? extends E> c);
// 刪除元素
boolean remove(Object o);
// 判斷是否包含元素
boolean contains(Object o);
// 獲取迭代器
Iterator<E> iterator();
// 其他方法(size()、isEmpty()、clear() 等)
}
?? 關(guān)鍵注意點(diǎn):
- Set 的去重機(jī)制依賴(lài)元素的 equals() 方法,但為了提高查找效率,會(huì)先通過(guò) hashCode() 方法計(jì)算哈希值,因此重寫(xiě) equals() 方法時(shí)必須重寫(xiě) hashCode() 方法,否則會(huì)導(dǎo)致去重失效
- Set 接口沒(méi)有提供基于索引的訪問(wèn)方法(如 get(int index)),因?yàn)槠湓O(shè)計(jì)初衷不強(qiáng)調(diào)元素的順序訪問(wèn)
- 所有 Set 實(shí)現(xiàn)類(lèi)均為非線(xiàn)程安全(除 ConcurrentSkipListSet 等并發(fā)實(shí)現(xiàn)類(lèi)),多線(xiàn)程環(huán)境下需手動(dòng)保證線(xiàn)程安全
2.1 Set與List接口核心區(qū)別對(duì)比
| 特性 | Set 接口 | List 接口 |
|---|---|---|
| 元素重復(fù)性 | 不可重復(fù)(基于 equals()) | 可重復(fù) |
| 元素有序性 | 無(wú)序(部分實(shí)現(xiàn)類(lèi)除外) | 有序(插入順序=遍歷順序) |
| 索引訪問(wèn)支持 | 不支持 | 支持(get/set 等方法) |
| 底層實(shí)現(xiàn)依賴(lài) | 哈希表/紅黑樹(shù)(去重/排序) | 動(dòng)態(tài)數(shù)組/雙向鏈表(訪問(wèn)) |
| 核心用途 | 去重、無(wú)序存儲(chǔ)、排序存儲(chǔ) | 有序存儲(chǔ)、索引訪問(wèn)、重復(fù)元素存儲(chǔ) |
三、HashSet深度解析:哈希表實(shí)現(xiàn)的高效去重集合
3.1 底層數(shù)據(jù)結(jié)構(gòu)與核心成員變量
?? HashSet 是 Set 接口最常用的實(shí)現(xiàn)類(lèi),其底層基于哈希表(HashMap) 實(shí)現(xiàn),利用 HashMap 的 key 不可重復(fù)特性實(shí)現(xiàn) Set 的去重功能。本質(zhì)上,HashSet 就是一個(gè)“精簡(jiǎn)版”的 HashMap——只使用 key 存儲(chǔ)元素,value 固定為一個(gè)靜態(tài)常量對(duì)象。
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// 底層存儲(chǔ)核心:HashMap 對(duì)象
private transient HashMap<E, Object> map;
// 固定的 value 對(duì)象,所有 key 都映射到該對(duì)象
private static final Object PRESENT = new Object();
// 無(wú)參構(gòu)造:創(chuàng)建空的 HashMap
public HashSet() {
map = new HashMap<>();
}
// 指定初始容量的構(gòu)造函數(shù)
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
// 指定初始容量和負(fù)載因子的構(gòu)造函數(shù)
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
}哈希表結(jié)構(gòu)示意圖(基于 JDK 8,數(shù)組+鏈表/紅黑樹(shù)):
哈希表(HashMap)
┌─────────┬─────────┬─────────┬─────────┐
│ 桶 0 │ 桶 1 │ 桶 2 │ 桶 3 │
├─────────┼─────────┼─────────┼─────────┤
│ null │ 鏈表 │ 紅黑樹(shù) │ null │
│ │ (A→B→C) │ (D→E→F) │ │
└─────────┴─────────┴─────────┴─────────┘
↑ ↑ ↑
| | |
HashSet 元素:A、B、C、D、E、F(存儲(chǔ)在 HashMap 的 key 中)
- 哈希表的核心是“數(shù)組+鏈表+紅黑樹(shù)”的組合結(jié)構(gòu),用于解決哈希沖突
- 每個(gè)數(shù)組元素稱(chēng)為一個(gè)“桶(Bucket)”,桶中存儲(chǔ)的是哈希值相同的元素(哈希沖突元素)
- 當(dāng)桶中元素個(gè)數(shù)超過(guò)閾值(默認(rèn) 8)且數(shù)組長(zhǎng)度大于 64 時(shí),鏈表會(huì)轉(zhuǎn)為紅黑樹(shù),提高查詢(xún)效率
3.2 核心方法源碼解析(基于HashMap委托實(shí)現(xiàn))
HashSet 的所有核心方法均委托給底層的 HashMap 實(shí)現(xiàn),因此理解 HashMap 的工作原理是掌握 HashSet 的關(guān)鍵。
add(E e) 方法:元素添加與去重機(jī)制
public boolean add(E e) {
// 調(diào)用 HashMap 的 put 方法,key 為待添加元素,value 為 PRESENT
// HashMap 的 put 方法返回 null 表示添加成功(key 不存在),返回舊 value 表示添加失?。╧ey 已存在)
return map.put(e, PRESENT) == null;
}?? 去重機(jī)制核心流程:
1. 當(dāng)調(diào)用 add(E e) 方法時(shí),HashSet 會(huì)將元素 e 作為 key 傳入 HashMap 的 put 方法
2. HashMap 首先調(diào)用 e 的 hashCode() 方法計(jì)算哈希值,根據(jù)哈希值確定元素在數(shù)組中的桶位置
3. 若桶位置為空,則直接創(chuàng)建節(jié)點(diǎn)存入,添加成功(返回 true)
4. 若桶位置不為空(發(fā)生哈希沖突),則通過(guò) equals() 方法比較桶中已有元素與 e:
- 若存在 equals() 返回 true 的元素,則說(shuō)明 e 已存在,添加失?。ǚ祷?false)
- 若不存在,則將 e 加入桶中(鏈表或紅黑樹(shù)),添加成功(返回 true)
?? 關(guān)鍵結(jié)論:HashSet 的去重依賴(lài) hashCode() + equals() 方法的協(xié)同工作:
- 若兩個(gè)元素的 hashCode() 返回值不同,HashSet 直接認(rèn)為它們是不同元素,不會(huì)調(diào)用 equals()
- 若兩個(gè)元素的 hashCode() 返回值相同,才會(huì)調(diào)用 equals() 進(jìn)一步判斷是否為同一元素
- 因此,若只重寫(xiě) equals() 而不重寫(xiě) hashCode(),會(huì)導(dǎo)致 hashCode() 不同但 equals() 相同的元素被重復(fù)添加(因?yàn)楣V挡煌嫒氩煌?,不?huì)觸發(fā) equals() 比較)
contains(Object o)方法:元素查找機(jī)制
public boolean contains(Object o) {
// 委托給 HashMap 的 containsKey 方法
return map.containsKey(o);
}
HashMap 的 containsKey 方法查找流程:
- 計(jì)算 o 的 hashCode() 得到哈希值,確定桶位置
- 遍歷桶中的元素(鏈表或紅黑樹(shù)),通過(guò) equals() 方法比較是否存在匹配元素
- 找到則返回 true,否則返回 false
?? 哈希表的查找效率極高,理想情況下(無(wú)哈希沖突)時(shí)間復(fù)雜度為 O(1),最壞情況下(所有元素哈希沖突,鏈表結(jié)構(gòu))時(shí)間復(fù)雜度為 O(n),紅黑樹(shù)結(jié)構(gòu)下為 O(log n)
remove(Object o)方法:元素刪除機(jī)制
public boolean remove(Object o) {
// 委托給 HashMap 的 remove 方法,返回 PRESENT 表示刪除成功
return map.remove(o) == PRESENT;
}
刪除流程與查找流程類(lèi)似:先通過(guò) hashCode() 定位桶位置,再通過(guò) equals() 找到目標(biāo)元素,最后刪除該元素(并維護(hù)鏈表/紅黑樹(shù)結(jié)構(gòu))。
3.3 哈希沖突與解決策略
哈希沖突的產(chǎn)生
當(dāng)兩個(gè)不同元素的 hashCode() 方法返回相同的哈希值時(shí),它們會(huì)被分配到哈希表的同一個(gè)桶中,這種情況稱(chēng)為哈希沖突。
例如:
class Person {
private String name;
private int age;
// 僅重寫(xiě) equals(),未重寫(xiě) hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
// 省略構(gòu)造函數(shù)和 getter/setter
}
// 測(cè)試:兩個(gè) equals() 相同但 hashCode() 不同的對(duì)象
Person p1 = new Person("張三", 20);
Person p2 = new Person("張三", 20);
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // false(Object 類(lèi)的 hashCode() 基于對(duì)象地址)
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 2(去重失效!)哈希沖突的解決策略(HashMap/HashSet實(shí)現(xiàn))
- 鏈地址法(拉鏈法):將同一個(gè)桶中的沖突元素以鏈表形式存儲(chǔ),JDK 8 中當(dāng)鏈表長(zhǎng)度超過(guò) 8 且數(shù)組長(zhǎng)度≥64 時(shí),轉(zhuǎn)為紅黑樹(shù)
- 擾動(dòng)函數(shù):對(duì) hashCode() 的返回值進(jìn)行二次哈希計(jì)算(JDK 8 簡(jiǎn)化為一次異或和無(wú)符號(hào)右移),減少哈希沖突的概率
- 動(dòng)態(tài)擴(kuò)容:當(dāng)哈希表的負(fù)載因子(元素個(gè)數(shù)/數(shù)組容量)超過(guò)閾值(默認(rèn) 0.75)時(shí),數(shù)組容量擴(kuò)容為原來(lái)的 2 倍,重新分配所有元素的桶位置
正確重寫(xiě) hashCode() 與 equals() 方法
為避免哈希沖突導(dǎo)致的去重失效,必須遵循以下重寫(xiě)原則:
- 自反性:x.equals(x) 必須返回 true
- 對(duì)稱(chēng)性:若 x.equals(y) 為 true,則 y.equals(x) 也必須為 true
- 傳遞性:若 x.equals(y) 為 true 且 y.equals(z) 為 true,則 x.equals(z) 必須為 true
- 一致性:若 x 和 y 的equals() 比較所依賴(lài)的屬性未變,則 x.equals(y) 的結(jié)果始終不變
- 哈希一致性:若 x.equals(y) 為 true,則 x.hashCode() 必須等于 y.hashCode();若 x.equals(y) 為 false,x.hashCode() 與 y.hashCode() 可以相等(但盡量不同,減少?zèng)_突)
正確重寫(xiě)示例:
class Person {
private String name;
private int age;
// 構(gòu)造函數(shù)、getter/setter 省略
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
// 基于 equals() 依賴(lài)的屬性計(jì)算哈希值
return Objects.hash(name, age);
}
}
// 測(cè)試:去重生效
Person p1 = new Person("張三", 20);
Person p2 = new Person("張三", 20);
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 1(正確去重)3.4 HashSet性能分析與使用場(chǎng)景
3.4.1 性能特點(diǎn)
| 操作類(lèi)型 | 時(shí)間復(fù)雜度 | 說(shuō)明 |
|---|---|---|
| add()/contains()/remove() | O(1)(平均情況) | 無(wú)哈希沖突時(shí)效率最高,沖突嚴(yán)重時(shí)下降 |
| 遍歷(iterator()) | O(n) | 需遍歷所有桶和元素 |
| 擴(kuò)容 | O(n) | 數(shù)組擴(kuò)容時(shí)需重新哈希并遷移所有元素 |
3.4.2 關(guān)鍵性能參數(shù)
- 初始容量:默認(rèn) 16(HashMap 的默認(rèn)初始容量),可通過(guò)構(gòu)造函數(shù)指定
- 負(fù)載因子:默認(rèn) 0.75,負(fù)載因子越大,哈希表越滿(mǎn),沖突概率越高,查詢(xún)效率越低;負(fù)載因子越小,哈希表越空,內(nèi)存浪費(fèi)越多
- 樹(shù)化閾值:默認(rèn) 8,桶中元素個(gè)數(shù)超過(guò) 8 且數(shù)組長(zhǎng)度≥64 時(shí),鏈表轉(zhuǎn)為紅黑樹(shù)
- 反樹(shù)化閾值:默認(rèn) 6,桶中元素個(gè)數(shù)少于 6 時(shí),紅黑樹(shù)轉(zhuǎn)回鏈表
3.4.3 適用場(chǎng)景
- 無(wú)需保證元素順序,僅需高效去重的場(chǎng)景
- 頻繁進(jìn)行添加、刪除、查找操作,對(duì)性能要求較高的場(chǎng)景
- 元素類(lèi)型已正確重寫(xiě) hashCode() 和 equals() 方法的場(chǎng)景
3.4.4 性能優(yōu)化技巧
?? 技巧 1:初始化時(shí)指定合理的初始容量,減少擴(kuò)容次數(shù)。若已知元素?cái)?shù)量為 N,推薦初始容量設(shè)置為 N / 負(fù)載因子 + 1(例如 N=1000,負(fù)載因子 0.75,初始容量=1000/0.75+1≈1334)
// 推薦:已知元素約 1000 個(gè),指定初始容量 1334 HashSet<String> set = new HashSet<>(1334);
?? 技巧 2:避免使用哈希值易沖突的元素類(lèi)型,或確保 hashCode() 方法的哈希分布均勻,減少?zèng)_突
?? 技巧 3:?jiǎn)尉€(xiàn)程環(huán)境使用 HashSet,多線(xiàn)程環(huán)境可使用 Collections.synchronizedSet(new HashSet<>()) 或 ConcurrentHashMap.newKeySet()(JUC 提供,性能更優(yōu))
四、LinkedHashSet深度解析:有序去重的哈希集合
4.1 底層數(shù)據(jù)結(jié)構(gòu)與核心特性
?? LinkedHashSet 繼承自 HashSet,其底層基于哈希表(HashMap)+ 雙向鏈表實(shí)現(xiàn),在 HashSet 去重功能的基礎(chǔ)上,額外保證了元素的插入順序(遍歷順序與插入順序一致)。
public class LinkedHashSet<E> extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// 構(gòu)造函數(shù):調(diào)用父類(lèi) HashSet 的構(gòu)造函數(shù),創(chuàng)建 LinkedHashMap 實(shí)例
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
// 父類(lèi) HashSet 中對(duì)應(yīng)的構(gòu)造函數(shù)(包訪問(wèn)權(quán)限)
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
}核心結(jié)構(gòu)示意圖:
哈希表(LinkedHashMap)
┌─────────┬─────────┬─────────┐
│ 桶 0 │ 桶 1 │ 桶 2 │
├─────────┼─────────┼─────────┤
│ A │ B→C │ D │
└─────────┴─────────┴─────────┘
↑ ↑ ↑
│ │ │
雙向鏈表:A ←→ B ←→ C ←→ D(維護(hù)插入順序)
- LinkedHashSet 的底層實(shí)際是 LinkedHashMap,而 LinkedHashMap 是 HashMap 的子類(lèi),在 HashMap 的基礎(chǔ)上增加了一條雙向鏈表
- 雙向鏈表用于維護(hù)元素的插入順序,哈希表用于保證去重和高效查詢(xún)
- 遍歷 LinkedHashSet 時(shí),實(shí)際是遍歷雙向鏈表,因此順序與插入順序一致
4.2 核心特性與源碼解析
4.2.1 有序性保障原理
LinkedHashMap 中的每個(gè)節(jié)點(diǎn)(Entry)除了包含 HashMap 的 key、value、hash、next 字段外,還新增了兩個(gè)指針(before 和 after),用于構(gòu)建雙向鏈表:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 雙向鏈表的前驅(qū)和后繼指針
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
當(dāng)調(diào)用 add(E e) 方法時(shí),元素會(huì)同時(shí)被添加到哈希表和雙向鏈表中:
- 哈希表部分:與 HashSet 邏輯一致,通過(guò) hashCode() 和 equals() 保證去重
- 雙向鏈表部分:新元素會(huì)被添加到雙向鏈表的尾部,從而維護(hù)插入順序
4.2.2 遍歷效率與性能特點(diǎn)
LinkedHashSet 的遍歷效率高于 HashSet,因?yàn)楸闅v是通過(guò)雙向鏈表進(jìn)行的,無(wú)需遍歷哈希表中的空桶;而 HashSet 遍歷需要遍歷整個(gè)哈希表數(shù)組,包括空桶。
性能對(duì)比(以 100 萬(wàn)條元素為例):
| 操作類(lèi)型 | HashSet | LinkedHashSet |
|---|---|---|
| 添加 100 萬(wàn)元素 | 89ms | 102ms |
| 查找單個(gè)元素 | 0ms | 0ms |
| 遍歷 100 萬(wàn)元素 | 12ms | 8ms |
?? 結(jié)論:LinkedHashSet 的添加操作略慢于 HashSet(因?yàn)樾枰S護(hù)雙向鏈表),但遍歷效率更高;查找和刪除效率與 HashSet 基本一致(均依賴(lài)哈希表)。
4.2.3 訪問(wèn)順序模式(LinkedHashSet 擴(kuò)展特性)
LinkedHashMap 支持兩種順序模式:
- 插入順序(默認(rèn)):遍歷順序與元素插入順序一致
- 訪問(wèn)順序:遍歷順序與元素最后訪問(wèn)時(shí)間一致(get() 或 put() 操作會(huì)將元素移到雙向鏈表尾部)
雖然 LinkedHashSet 本身未直接提供訪問(wèn)順序的構(gòu)造函數(shù),但可通過(guò)自定義 LinkedHashMap 實(shí)現(xiàn):
// 自定義訪問(wèn)順序的 LinkedHashSet
class AccessOrderLinkedHashSet<E> extends LinkedHashSet<E> {
public AccessOrderLinkedHashSet(int initialCapacity, float loadFactor) {
super(new LinkedHashMap<>(initialCapacity, loadFactor, true));
}
}
// 測(cè)試訪問(wèn)順序
public class TestAccessOrder {
public static void main(String[] args) {
AccessOrderLinkedHashSet<String> set = new AccessOrderLinkedHashSet<>(16, 0.75f);
set.add("A");
set.add("B");
set.add("C");
System.out.println("初始遍歷:" + new ArrayList<>(set)); // [A, B, C](插入順序)
set.contains("B"); // 訪問(wèn) B 元素
System.out.println("訪問(wèn) B 后遍歷:" + new ArrayList<>(set)); // [A, C, B](B 移到尾部)
}
}4.3 LinkedHashSet 適用場(chǎng)景與注意事項(xiàng)
4.3.1 適用場(chǎng)景
- 需要保證元素去重,且需要維護(hù)插入順序的場(chǎng)景(如日志記錄、歷史操作記錄等)
- 頻繁遍歷集合的場(chǎng)景(遍歷效率高于 HashSet)
- 對(duì)元素順序有要求,但無(wú)需排序的場(chǎng)景
4.3.2 注意事項(xiàng)
?? 注意 1:LinkedHashSet 的內(nèi)存占用高于 HashSet,因?yàn)槊總€(gè)節(jié)點(diǎn)需要額外存儲(chǔ) before 和 after 指針,因此在內(nèi)存敏感場(chǎng)景需謹(jǐn)慎使用
?? 注意 2:LinkedHashSet 的有序性是“插入順序”,而非“自然順序”或“自定義排序順序”,若需要排序功能,需使用 TreeSet
?? 注意 3:與 HashSet 一樣,LinkedHashSet 非線(xiàn)程安全,多線(xiàn)程環(huán)境需手動(dòng)同步
五、TreeSet 深度解析:基于紅黑樹(shù)的排序集合
5.1 底層數(shù)據(jù)結(jié)構(gòu)與核心特性
?? TreeSet 是 Set 接口的排序?qū)崿F(xiàn)類(lèi),底層基于紅黑樹(shù)(TreeMap) 實(shí)現(xiàn),其核心特性是元素有序性(自然排序或定制排序)和不可重復(fù)性。
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable {
// 底層存儲(chǔ)核心:TreeMap 對(duì)象
private transient NavigableMap<E, Object> m;
// 固定的 value 對(duì)象,與 HashSet 類(lèi)似
private static final Object PRESENT = new Object();
// 構(gòu)造函數(shù):創(chuàng)建 TreeMap 實(shí)例
public TreeSet() {
this(new TreeMap<>());
}
// 自定義比較器的構(gòu)造函數(shù)
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
TreeSet(NavigableMap<E, Object> m) {
this.m = m;
}
}紅黑樹(shù)結(jié)構(gòu)示意圖(有序二叉查找樹(shù)):
8(根節(jié)點(diǎn),黑色)
/ \
3(紅) 10(紅)
/ \ \
1(黑)5(黑) 14(黑)
/ \ /
4(紅)6(紅)13(紅)
紅黑樹(shù)的核心特性(保證樹(shù)的平衡,提高查詢(xún)效率):
- 每個(gè)節(jié)點(diǎn)要么是紅色,要么是黑色
- 根節(jié)點(diǎn)是黑色
- 所有葉子節(jié)點(diǎn)(NIL 節(jié)點(diǎn))是黑色
- 若一個(gè)節(jié)點(diǎn)是紅色,則其兩個(gè)子節(jié)點(diǎn)都是黑色
- 從任意節(jié)點(diǎn)到其所有葉子節(jié)點(diǎn)的路徑上,黑色節(jié)點(diǎn)的數(shù)量相同
5.2 排序機(jī)制:自然排序與定制排序
TreeSet 的有序性依賴(lài)于比較器(Comparator) 或元素自身實(shí)現(xiàn)的Comparable 接口,分為兩種排序方式:
5.2.1 自然排序(默認(rèn))
若元素實(shí)現(xiàn)了 Comparable 接口,TreeSet 會(huì)調(diào)用元素的 compareTo() 方法進(jìn)行排序,這就是自然排序。
常見(jiàn)的實(shí)現(xiàn)了 Comparable 接口的類(lèi):
- 基本類(lèi)型包裝類(lèi)(Integer、Double、String 等):按數(shù)值/字典順序排序
- 自定義類(lèi):需手動(dòng)實(shí)現(xiàn) Comparable 接口,重寫(xiě) compareTo() 方法
示例:自定義類(lèi)的自然排序
class Student implements Comparable<Student> {
private String id;
private int score;
// 構(gòu)造函數(shù)、getter/setter 省略
// 按分?jǐn)?shù)降序排序,分?jǐn)?shù)相同按學(xué)號(hào)升序排序
@Override
public int compareTo(Student o) {
if (this.score != o.score) {
return o.score - this.score; // 降序(o.score - this.score)
}
return this.id.compareTo(o.id); // 升序
}
@Override
public String toString() {
return "Student{id='" + id + "', score=" + score + "}";
}
}
// 測(cè)試自然排序
public class TreeSetNaturalSortTest {
public static void main(String[] args) {
TreeSet<Student> set = new TreeSet<>();
set.add(new Student("001", 90));
set.add(new Student("002", 85));
set.add(new Student("003", 90));
set.add(new Student("004", 95));
// 遍歷:按分?jǐn)?shù)降序,分?jǐn)?shù)相同按學(xué)號(hào)升序
for (Student s : set) {
System.out.println(s);
}
// 輸出結(jié)果:
// Student{id='004', score=95}
// Student{id='001', score=90}
// Student{id='003', score=90}
// Student{id='002', score=85}
}
}5.2.2 定制排序(自定義比較器)
若元素未實(shí)現(xiàn) Comparable 接口,或需要自定義排序規(guī)則,可通過(guò) TreeSet 的構(gòu)造函數(shù)傳入 Comparator 接口實(shí)現(xiàn)類(lèi),這就是定制排序。
示例:基于 Comparator 的定制排序
class Employee {
private String name;
private int age;
// 構(gòu)造函數(shù)、getter/setter、toString 省略
}
// 測(cè)試定制排序(按年齡升序)
public class TreeSetCustomSortTest {
public static void main(String[] args) {
// 傳入 Comparator 匿名內(nèi)部類(lèi)(Java 8+ 可簡(jiǎn)化為 Lambda 表達(dá)式)
TreeSet<Employee> set = new TreeSet<>((e1, e2) -> e1.getAge() - e2.getAge());
set.add(new Employee("張三", 25));
set.add(new Employee("李四", 22));
set.add(new Employee("王五", 28));
for (Employee e : set) {
System.out.println(e);
}
// 輸出結(jié)果:
// Employee{name='李四', age=22}
// Employee{name='張三', age=25}
// Employee{name='王五', age=28}
}
}5.2.3 去重機(jī)制(基于排序規(guī)則)
TreeSet 的去重機(jī)制與 HashSet 不同,它不依賴(lài) hashCode() 和 equals() 方法,而是基于排序規(guī)則(compareTo() 或 compare() 方法):
- 若兩個(gè)元素的比較結(jié)果為 0(compareTo() 返回 0 或 compare() 返回 0),則 TreeSet 認(rèn)為它們是相同元素,會(huì)拒絕添加
- 因此,使用 TreeSet 時(shí),若元素實(shí)現(xiàn)了 Comparable 接口,建議重寫(xiě) equals() 方法,使 equals() 的結(jié)果與 compareTo() 保持一致(即 compareTo() 返回 0 時(shí),equals() 也返回 true)
5.3 核心方法源碼解析(基于 TreeMap 委托實(shí)現(xiàn))
TreeSet 的核心方法均委托給底層的 TreeMap 實(shí)現(xiàn),TreeMap 的核心是紅黑樹(shù)的插入、刪除、查找操作。
add(E e) 方法:元素添加與紅黑樹(shù)插入
public boolean add(E e) {
// 調(diào)用 TreeMap 的 put 方法,key 為元素 e,value 為 PRESENT
return m.put(e, PRESENT) == null;
}
TreeMap 的 put 方法核心流程:
- 若紅黑樹(shù)為空,創(chuàng)建根節(jié)點(diǎn)
- 若存在比較器,使用比較器的 compare() 方法查找插入位置;否則使用元素的 compareTo() 方法
- 若找到相同元素(比較結(jié)果為 0),則替換 value,返回舊 value(TreeSet 認(rèn)為添加失?。?/li>
- 若未找到相同元素,創(chuàng)建新節(jié)點(diǎn)并插入紅黑樹(shù)
- 調(diào)整紅黑樹(shù)結(jié)構(gòu)(變色、旋轉(zhuǎn)),保證紅黑樹(shù)的平衡特性
紅黑樹(shù)插入調(diào)整示例(以插入節(jié)點(diǎn) 7 為例):
- 插入節(jié)點(diǎn) 7 作為紅色節(jié)點(diǎn),發(fā)現(xiàn)父節(jié)點(diǎn) 6 也是紅色,違反紅黑樹(shù)特性 4
- 進(jìn)行變色操作:將祖父節(jié)點(diǎn) 5 變?yōu)榧t色,父節(jié)點(diǎn) 6 和叔父節(jié)點(diǎn) 4 變?yōu)楹谏?/li>
- 若祖父節(jié)點(diǎn)是根節(jié)點(diǎn),再將其變?yōu)楹谏?,調(diào)整完成
導(dǎo)航方法(NavigableSet接口)
TreeSet 實(shí)現(xiàn)了 NavigableSet 接口,提供了一系列強(qiáng)大的導(dǎo)航方法,用于查找元素的前驅(qū)、后繼、范圍查詢(xún)等:
// 查找小于 e 的最大元素 E lower(E e); // 查找小于等于 e 的最大元素 E floor(E e); // 查找大于等于 e 的最小元素 E ceiling(E e); // 查找大于 e 的最小元素 E higher(E e); // 刪除并返回最小元素 E pollFirst(); // 刪除并返回最大元素 E pollLast(); // 獲取升序迭代器 Iterator<E> iterator(); // 獲取降序迭代器 Iterator<E> descendingIterator();
示例:導(dǎo)航方法使用
TreeSet<Integer> set = new TreeSet<>(); set.add(10); set.add(20); set.add(30); set.add(40); System.out.println(set.lower(25)); // 20(小于 25 的最大元素) System.out.println(set.floor(25)); // 20(小于等于 25 的最大元素) System.out.println(set.ceiling(25)); // 30(大于等于 25 的最小元素) System.out.println(set.higher(25)); // 30(大于 25 的最小元素) System.out.println(set.pollFirst()); // 10(刪除最小元素) System.out.println(set.pollLast()); // 40(刪除最大元素)
5.4 TreeSet 性能分析與使用場(chǎng)景
5.4.1 性能特點(diǎn)
| 操作類(lèi)型 | 時(shí)間復(fù)雜度 | 說(shuō)明 |
|---|---|---|
| add()/contains()/remove() | O(log n) | 紅黑樹(shù)的平衡特性保證了對(duì)數(shù)時(shí)間復(fù)雜度 |
| 導(dǎo)航方法(lower/floor/ceiling/higher) | O(log n) | 基于紅黑樹(shù)的二分查找 |
| 遍歷(iterator()) | O(n) | 紅黑樹(shù)的中序遍歷,按排序順序輸出 |
5.4.2 適用場(chǎng)景
- 需要對(duì)元素進(jìn)行排序存儲(chǔ)的場(chǎng)景(自然排序或定制排序)
- 需要頻繁進(jìn)行范圍查詢(xún)、前驅(qū)/后繼查找的場(chǎng)景(如排行榜、區(qū)間統(tǒng)計(jì)等)
- 元素?cái)?shù)量較多,且需要保證有序性和去重的場(chǎng)景
5.4.3 注意事項(xiàng)
?? 注意 1:TreeSet 的添加、刪除、查找效率低于 HashSet/LinkedHashSet(O(log n) vs O(1)),因此無(wú)需排序時(shí),優(yōu)先選擇 HashSet/LinkedHashSet
?? 注意 2:TreeSet 非線(xiàn)程安全,多線(xiàn)程環(huán)境下需使用 Collections.synchronizedSortedSet(new TreeSet<>()) 或 JUC 提供的 ConcurrentSkipListSet
?? 注意 3:若元素未實(shí)現(xiàn) Comparable 接口且未指定比較器,添加元素時(shí)會(huì)拋出 ClassCastException
六、三大Set實(shí)現(xiàn)類(lèi)核心對(duì)比與選型指南
6.1 核心特性對(duì)比表
| 特性 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底層數(shù)據(jù)結(jié)構(gòu) | 哈希表(數(shù)組+鏈表/紅黑樹(shù)) | 哈希表+雙向鏈表 | 紅黑樹(shù)(TreeMap) |
| 元素有序性 | 無(wú)序 | 插入順序 | 自然排序/定制排序 |
| 去重機(jī)制 | hashCode() + equals() | hashCode() + equals() | compareTo()/compare() |
| 時(shí)間復(fù)雜度(增刪查) | O(1)(平均) | O(1)(平均) | O(log n) |
| 內(nèi)存占用 | 較低 | 較高(額外存儲(chǔ)鏈表指針) | 中等(紅黑樹(shù)節(jié)點(diǎn) overhead) |
| 線(xiàn)程安全 | 否 | 否 | 否 |
| 導(dǎo)航方法支持 | 不支持 | 不支持 | 支持(NavigableSet) |
| 適用場(chǎng)景 | 高效去重,無(wú)需有序 | 去重+維護(hù)插入順序 | 去重+排序+范圍查詢(xún) |
6.2 選型決策流程圖
開(kāi)始 → 需要元素有序?
├─ 否 → 需要頻繁遍歷?
│ ├─ 是 → 選擇 LinkedHashSet
│ └─ 否 → 選擇 HashSet(高效去重)
└─ 是 → 需要排序(自然/定制)?
├─ 是 → 需要范圍查詢(xún)/導(dǎo)航?
│ ├─ 是 → 選擇 TreeSet
│ └─ 否 → 選擇 LinkedHashSet(插入順序)
└─ 否 → 選擇 LinkedHashSet(插入順序)
6.3 實(shí)戰(zhàn)選型示例
示例 1:用戶(hù)注冊(cè)時(shí)存儲(chǔ)用戶(hù)名(需去重,無(wú)需有序)
// 選型:HashSet(高效去重,性能最優(yōu))
Set<String> usernames = new HashSet<>();
usernames.add("zhangsan");
usernames.add("lisi");
// 重復(fù)添加會(huì)失敗
usernames.add("zhangsan");
System.out.println(usernames.size()); // 1
示例 2:記錄用戶(hù)操作日志(需去重,維護(hù)操作順序)
// 選型:LinkedHashSet(去重+插入順序)
Set<String> operationLogs = new LinkedHashSet<>();
operationLogs.add("用戶(hù)登錄");
operationLogs.add("查詢(xún)數(shù)據(jù)");
operationLogs.add("修改數(shù)據(jù)");
operationLogs.add("用戶(hù)退出");
// 遍歷順序與插入順序一致
for (String log : operationLogs) {
System.out.println(log); // 用戶(hù)登錄 → 查詢(xún)數(shù)據(jù) → 修改數(shù)據(jù) → 用戶(hù)退出
}
示例 3:學(xué)生成績(jī)排行榜(需去重,按分?jǐn)?shù)降序排序,支持TopN查詢(xún))
// 選型:TreeSet(去重+排序+導(dǎo)航方法)
TreeSet<Student> scoreRank = new TreeSet<>((s1, s2) -> s2.getScore() - s1.getScore());
scoreRank.add(new Student("001", 90));
scoreRank.add(new Student("002", 95));
scoreRank.add(new Student("003", 88));
// Top3 查詢(xún)(升序迭代器取前3個(gè))
Iterator<Student> iterator = scoreRank.iterator();
int count = 0;
System.out.println("成績(jī)排行榜 Top3:");
while (iterator.hasNext() && count < 3) {
System.out.println(++count + ":" + iterator.next());
}七、實(shí)戰(zhàn)案例:基于Set的電商商品標(biāo)簽管理系統(tǒng)
7.1 需求分析
設(shè)計(jì)一個(gè)電商商品標(biāo)簽管理系統(tǒng),支持以下核心功能:
- 為商品添加標(biāo)簽(標(biāo)簽不可重復(fù),需維護(hù)添加順序)
- 為商品刪除指定標(biāo)簽
- 查詢(xún)商品的所有標(biāo)簽(按添加順序展示)
- 查詢(xún)包含指定標(biāo)簽的所有商品
- 統(tǒng)計(jì)所有標(biāo)簽的使用頻次(按頻次降序排序)
- 批量導(dǎo)入商品標(biāo)簽,自動(dòng)去重
7.2 設(shè)計(jì)思路
- 商品類(lèi)(Product):包含商品ID、商品名稱(chēng)、標(biāo)簽集合(使用 LinkedHashSet 保證去重和插入順序)
- 標(biāo)簽管理類(lèi)(TagManager):維護(hù)商品與標(biāo)簽的映射關(guān)系,提供標(biāo)簽添加、刪除、查詢(xún)、統(tǒng)計(jì)功能
- 統(tǒng)計(jì)功能:使用 TreeSet 按標(biāo)簽頻次降序排序,使用 HashMap 存儲(chǔ)標(biāo)簽頻次
7.3 代碼實(shí)現(xiàn)
7.3.1 商品類(lèi)(Product.java)
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 商品類(lèi)
*/
public class Product {
private String productId; // 商品ID
private String productName; // 商品名稱(chēng)
private Set<String> tags; // 商品標(biāo)簽(LinkedHashSet:去重+插入順序)
// 構(gòu)造函數(shù)
public Product(String productId, String productName) {
this.productId = productId;
this.productName = productName;
this.tags = new LinkedHashSet<>();
}
// 添加標(biāo)簽(去重)
public boolean addTag(String tag) {
if (tag == null || tag.trim().isEmpty()) {
throw new IllegalArgumentException("標(biāo)簽不能為空");
}
return tags.add(tag.trim());
}
// 批量添加標(biāo)簽
public boolean addTags(Set<String> tags) {
boolean success = false;
for (String tag : tags) {
if (addTag(tag)) {
success = true;
}
}
return success;
}
// 刪除標(biāo)簽
public boolean removeTag(String tag) {
if (tag == null || tag.trim().isEmpty()) {
return false;
}
return tags.remove(tag.trim());
}
// 獲取所有標(biāo)簽(返回不可修改集合,防止外部篡改)
public Set<String> getTags() {
return new LinkedHashSet<>(tags);
}
// getter/setter 省略
@Override
public String toString() {
return "Product{productId='" + productId + "', productName='" + productName + "', tags=" + tags + "}";
}
}7.3.2 標(biāo)簽管理類(lèi)(TagManager.java)
import java.util.*;
import java.util.stream.Collectors;
/**
* 標(biāo)簽管理類(lèi)
*/
public class TagManager {
// 存儲(chǔ)商品列表(key:商品ID,value:商品對(duì)象)
private Map<String, Product> productMap = new HashMap<>();
// 存儲(chǔ)標(biāo)簽頻次(key:標(biāo)簽,value:使用次數(shù))
private Map<String, Integer> tagFrequencyMap = new HashMap<>();
/**
* 添加商品
*/
public void addProduct(Product product) {
if (product == null || product.getProductId() == null) {
throw new IllegalArgumentException("商品信息不能為空");
}
productMap.put(product.getProductId(), product);
// 初始化商品標(biāo)簽的頻次
for (String tag : product.getTags()) {
tagFrequencyMap.put(tag, tagFrequencyMap.getOrDefault(tag, 0) + 1);
}
}
/**
* 為商品添加標(biāo)簽
*/
public boolean addTagToProduct(String productId, String tag) {
Product product = productMap.get(productId);
if (product == null) {
throw new IllegalArgumentException("商品不存在:" + productId);
}
// 商品添加標(biāo)簽(去重)
boolean success = product.addTag(tag);
if (success) {
// 更新標(biāo)簽頻次
tagFrequencyMap.put(tag, tagFrequencyMap.getOrDefault(tag, 0) + 1);
}
return success;
}
/**
* 為商品刪除標(biāo)簽
*/
public boolean removeTagFromProduct(String productId, String tag) {
Product product = productMap.get(productId);
if (product == null) {
throw new IllegalArgumentException("商品不存在:" + productId);
}
// 商品刪除標(biāo)簽
boolean success = product.removeTag(tag);
if (success) {
// 更新標(biāo)簽頻次
int frequency = tagFrequencyMap.getOrDefault(tag, 0);
if (frequency == 1) {
tagFrequencyMap.remove(tag);
} else {
tagFrequencyMap.put(tag, frequency - 1);
}
}
return success;
}
/**
* 查詢(xún)包含指定標(biāo)簽的所有商品
*/
public List<Product> queryProductsByTag(String tag) {
if (tag == null || tag.trim().isEmpty()) {
return Collections.emptyList();
}
tag = tag.trim();
// 遍歷所有商品,篩選包含該標(biāo)簽的商品
return productMap.values().stream()
.filter(product -> product.getTags().contains(tag))
.collect(Collectors.toList());
}
/**
* 統(tǒng)計(jì)標(biāo)簽使用頻次(按頻次降序排序)
*/
public Set<Map.Entry<String, Integer>> statTagFrequency() {
// 使用 TreeSet 按頻次降序排序,頻次相同按標(biāo)簽字典序升序
Set<Map.Entry<String, Integer>> sortedSet = new TreeSet<>((e1, e2) -> {
if (!e1.getValue().equals(e2.getValue())) {
return e2.getValue() - e1.getValue(); // 頻次降序
}
return e1.getKey().compareTo(e2.getKey()); // 標(biāo)簽升序
});
sortedSet.addAll(tagFrequencyMap.entrySet());
return sortedSet;
}
/**
* 批量導(dǎo)入商品標(biāo)簽(自動(dòng)去重)
*/
public void batchImportTags(String productId, List<String> tags) {
Product product = productMap.get(productId);
if (product == null) {
throw new IllegalArgumentException("商品不存在:" + productId);
}
// 批量添加標(biāo)簽(LinkedHashSet 自動(dòng)去重)
Set<String> tagSet = new LinkedHashSet<>(tags);
product.addTags(tagSet);
// 更新標(biāo)簽頻次
for (String tag : tagSet) {
tagFrequencyMap.put(tag, tagFrequencyMap.getOrDefault(tag, 0) + 1);
}
}
/**
* 獲取所有商品
*/
public List<Product> getAllProducts() {
return new ArrayList<>(productMap.values());
}
}7.3.3 測(cè)試類(lèi)(TagManagerTest.java)
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.Map;
/**
* 標(biāo)簽管理系統(tǒng)測(cè)試類(lèi)
*/
public class TagManagerTest {
public static void main(String[] args) {
TagManager tagManager = new TagManager();
// 1. 創(chuàng)建商品并添加到系統(tǒng)
System.out.println("=== 1. 創(chuàng)建商品并添加標(biāo)簽 ===");
Product p1 = new Product("P001", "Java編程思想");
p1.addTag("編程");
p1.addTag("Java");
p1.addTag("書(shū)籍");
p1.addTag("Java"); // 重復(fù)標(biāo)簽,自動(dòng)去重
tagManager.addProduct(p1);
Product p2 = new Product("P002", "Python數(shù)據(jù)分析");
p2.addTags(new LinkedHashSet<>(Arrays.asList("編程", "Python", "數(shù)據(jù)分析", "書(shū)籍")));
tagManager.addProduct(p2);
Product p3 = new Product("P003", "MySQL從入門(mén)到精通");
p3.addTags(new LinkedHashSet<>(Arrays.asList("數(shù)據(jù)庫(kù)", "MySQL", "編程", "書(shū)籍")));
tagManager.addProduct(p3);
// 查看所有商品
System.out.println("所有商品信息:");
tagManager.getAllProducts().forEach(System.out::println);
System.out.println();
// 2. 為商品添加新標(biāo)簽
System.out.println("=== 2. 為商品 P001 添加標(biāo)簽 '技術(shù)' ===");
boolean addSuccess = tagManager.addTagToProduct("P001", "技術(shù)");
System.out.println("添加結(jié)果:" + (addSuccess ? "成功" : "失?。?biāo)簽已存在)"));
System.out.println("P001 最新標(biāo)簽:" + tagManager.getAllProducts().get(0).getTags());
System.out.println();
// 3. 為商品刪除標(biāo)簽
System.out.println("=== 3. 為商品 P002 刪除標(biāo)簽 '數(shù)據(jù)分析' ===");
boolean removeSuccess = tagManager.removeTagFromProduct("P002", "數(shù)據(jù)分析");
System.out.println("刪除結(jié)果:" + (removeSuccess ? "成功" : "失敗(標(biāo)簽不存在)"));
System.out.println("P002 最新標(biāo)簽:" + tagManager.getAllProducts().get(1).getTags());
System.out.println();
// 4. 查詢(xún)包含標(biāo)簽 '編程' 的所有商品
System.out.println("=== 4. 查詢(xún)包含標(biāo)簽 '編程' 的商品 ===");
List<Product> programmingProducts = tagManager.queryProductsByTag("編程");
programmingProducts.forEach(System.out::println);
System.out.println();
// 5. 批量導(dǎo)入商品標(biāo)簽
System.out.println("=== 5. 為商品 P003 批量導(dǎo)入標(biāo)簽 ===");
List<String> batchTags = Arrays.asList("技術(shù)", "數(shù)據(jù)庫(kù)", "后端", "后端"); // 重復(fù)標(biāo)簽自動(dòng)去重
tagManager.batchImportTags("P003", batchTags);
System.out.println("P003 批量導(dǎo)入后的標(biāo)簽:" + tagManager.getAllProducts().get(2).getTags());
System.out.println();
// 6. 統(tǒng)計(jì)標(biāo)簽使用頻次
System.out.println("=== 6. 標(biāo)簽使用頻次統(tǒng)計(jì)(降序) ===");
Set<Map.Entry<String, Integer>> tagFrequency = tagManager.statTagFrequency();
for (Map.Entry<String, Integer> entry : tagFrequency) {
System.out.printf("標(biāo)簽:%s,使用次數(shù):%d%n", entry.getKey(), entry.getValue());
}
}
}7.4 測(cè)試結(jié)果與案例總結(jié)
7.4.1 測(cè)試結(jié)果(關(guān)鍵輸出)
=== 1. 創(chuàng)建商品并添加標(biāo)簽 ===
所有商品信息:
Product{productId='P001', productName='Java編程思想', tags=[編程, Java, 書(shū)籍]}
Product{productId='P002', productName='Python數(shù)據(jù)分析', tags=[編程, Python, 數(shù)據(jù)分析, 書(shū)籍]}
Product{productId='P003', productName='MySQL從入門(mén)到精通', tags=[數(shù)據(jù)庫(kù), MySQL, 編程, 書(shū)籍]}=== 2. 為商品 P001 添加標(biāo)簽 '技術(shù)' ===
添加結(jié)果:成功
P001 最新標(biāo)簽:[編程, Java, 書(shū)籍, 技術(shù)]=== 3. 為商品 P002 刪除標(biāo)簽 '數(shù)據(jù)分析' ===
刪除結(jié)果:成功
P002 最新標(biāo)簽:[編程, Python, 書(shū)籍]=== 4. 查詢(xún)包含標(biāo)簽 '編程' 的商品 ===
Product{productId='P001', productName='Java編程思想', tags=[編程, Java, 書(shū)籍, 技術(shù)]}
Product{productId='P002', productName='Python數(shù)據(jù)分析', tags=[編程, Python, 書(shū)籍]}
Product{productId='P003', productName='MySQL從入門(mén)到精通', tags=[數(shù)據(jù)庫(kù), MySQL, 編程, 書(shū)籍]}=== 5. 為商品 P003 批量導(dǎo)入標(biāo)簽 ===
P003 批量導(dǎo)入后的標(biāo)簽:[數(shù)據(jù)庫(kù), MySQL, 編程, 書(shū)籍, 技術(shù), 后端]=== 6. 標(biāo)簽使用頻次統(tǒng)計(jì)(降序) ===
標(biāo)簽:編程,使用次數(shù):3
標(biāo)簽:書(shū)籍,使用次數(shù):3
標(biāo)簽:技術(shù),使用次數(shù):2
標(biāo)簽:數(shù)據(jù)庫(kù),使用次數(shù):2
標(biāo)簽:Java,使用次數(shù):1
標(biāo)簽:MySQL,使用次數(shù):1
標(biāo)簽:Python,使用次數(shù):1
標(biāo)簽:后端,使用次數(shù):1
7.4.2 案例總結(jié)
? 本案例充分利用了三大 Set 實(shí)現(xiàn)類(lèi)的核心特性:
- LinkedHashSet:用于商品標(biāo)簽存儲(chǔ),保證去重和插入順序,滿(mǎn)足標(biāo)簽展示需求
- HashSet:批量導(dǎo)入標(biāo)簽時(shí)臨時(shí)存儲(chǔ),快速去重
- TreeSet:用于標(biāo)簽頻次統(tǒng)計(jì)排序,按頻次降序展示,滿(mǎn)足統(tǒng)計(jì)需求
? 關(guān)鍵技術(shù)亮點(diǎn):
- 數(shù)據(jù)安全性:getTags() 方法返回標(biāo)簽集合的副本,防止外部直接修改內(nèi)部數(shù)據(jù)
- 高效去重:通過(guò) LinkedHashSet 和 HashSet 實(shí)現(xiàn)不同場(chǎng)景下的快速去重
- 靈活排序:使用 TreeSet 自定義比較器,實(shí)現(xiàn)標(biāo)簽頻次的降序排序
- 性能優(yōu)化:使用 HashMap 維護(hù)商品映射和標(biāo)簽頻次,保證查詢(xún)和統(tǒng)計(jì)效率
? 擴(kuò)展方向:
- 多線(xiàn)程環(huán)境:將 LinkedHashSet 替換為
Collections.synchronizedSet(new LinkedHashSet<>()),HashMap 替換為 ConcurrentHashMap - 持久化存儲(chǔ):將商品和標(biāo)簽數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù),支持?jǐn)?shù)據(jù)持久化
- 標(biāo)簽?zāi):樵?xún):基于字符串匹配實(shí)現(xiàn)標(biāo)簽?zāi):樵?xún)功能
八、本章小結(jié)
本章深入解析了 Java 集合框架中三大核心 Set 實(shí)現(xiàn)類(lèi)(HashSet、LinkedHashSet、TreeSet)的底層數(shù)據(jù)結(jié)構(gòu)、核心原理、性能特點(diǎn)及適用場(chǎng)景,通過(guò)實(shí)戰(zhàn)案例展示了 Set 集合在實(shí)際開(kāi)發(fā)中的綜合應(yīng)用,同時(shí)總結(jié)了 Set 集合的選型技巧和常見(jiàn)問(wèn)題解決方案。
核心要點(diǎn)回顧:
- HashSet 基于哈希表實(shí)現(xiàn),核心優(yōu)勢(shì)是高效去重(O(1) 平均時(shí)間復(fù)雜度),適用于無(wú)需有序的去重場(chǎng)景
- LinkedHashSet 基于哈希表+雙向鏈表實(shí)現(xiàn),兼具去重和插入順序維護(hù)功能,遍歷效率高于 HashSet
- TreeSet 基于紅黑樹(shù)實(shí)現(xiàn),核心優(yōu)勢(shì)是排序和導(dǎo)航功能(O(log n) 時(shí)間復(fù)雜度),適用于需要排序和范圍查詢(xún)的場(chǎng)景
- Set 集合的去重機(jī)制:HashSet/LinkedHashSet 依賴(lài) hashCode() + equals(),TreeSet 依賴(lài) compareTo()/compare()
- 選型核心原則:根據(jù)是否需要有序、是否需要排序、操作頻次等因素選擇合適的 Set 實(shí)現(xiàn)類(lèi)
通過(guò)本章學(xué)習(xí),讀者應(yīng)能熟練掌握 Set 集合的使用技巧,根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景精準(zhǔn)選型,并能解決 Set 集合使用過(guò)程中的常見(jiàn)問(wèn)題(如去重失效、排序異常等)。下一章將深入學(xué)習(xí) Java 集合框架中的 Map 接口及其實(shí)現(xiàn)類(lèi)。
以上就是Java集合框架之Set的實(shí)現(xiàn)類(lèi)與實(shí)戰(zhàn)使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Java Set的實(shí)現(xiàn)類(lèi)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Nacos配置中心與本地代碼工程配置文件之間的優(yōu)先級(jí)關(guān)系詳解
本文介紹了Spring Cloud生態(tài)中配置加載原理,強(qiáng)調(diào)Nacos遠(yuǎn)程配置優(yōu)先級(jí)高于本地`application.yml`但低于命令行和環(huán)境變量,覆蓋了多環(huán)境配置、動(dòng)態(tài)刷新配置及安全配置外置等應(yīng)用場(chǎng)景,對(duì)比了Nacos配置中心與本地配置文件的優(yōu)缺點(diǎn),并給出最佳實(shí)踐建議2026-04-04
Java使用泛型Class實(shí)現(xiàn)消除模板代碼
Class作為實(shí)現(xiàn)反射功能的類(lèi),在開(kāi)發(fā)中經(jīng)常會(huì)用到,然而,當(dāng)Class遇上泛型后,事情就變得不是那么簡(jiǎn)單了,所以本文就來(lái)講講Java如何使用泛型Class實(shí)現(xiàn)消除模板代碼,需要的可以參考一下2023-06-06
詳談Java中Object類(lèi)中的方法以及finalize函數(shù)作用
下面小編就為大家?guī)?lái)一篇詳談Java中Object類(lèi)中的方法以及finalize函數(shù)作用。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
java迭代器基礎(chǔ)知識(shí)點(diǎn)總結(jié)
在本篇內(nèi)容里小編給大家整理了一篇關(guān)于java迭代器基礎(chǔ)知識(shí)點(diǎn)總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-01-01
Java如何利用LocalDate獲取某個(gè)月的第一天與最后一天日期
這篇文章主要給大家介紹了關(guān)于Java如何利用LocalDate獲取某個(gè)月的第一天與最后一天日期的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01
Java開(kāi)發(fā)工具IntelliJ IDEA安裝圖解
這篇文章主要介紹了Java開(kāi)發(fā)工具IntelliJ IDEA安裝圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
mybatis3使用@Select等注解實(shí)現(xiàn)增刪改查操作
這篇文章主要介紹了mybatis3使用@Select等注解實(shí)現(xiàn)增刪改查操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
SpringCloud Gateway 利用 Mysql 實(shí)現(xiàn)動(dòng)態(tài)路由的方法
這篇文章主要介紹了SpringCloud Gateway 利用 Mysql 實(shí)現(xiàn)動(dòng)態(tài)路由的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
詳解mybatis 批量更新數(shù)據(jù)兩種方法效率對(duì)比
這篇文章主要介紹了詳解mybatis 批量更新數(shù)據(jù)兩種方法效率對(duì)比,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02

