最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java集合框架之Set的實(shí)現(xiàn)類(lèi)與實(shí)戰(zhàn)使用詳解

 更新時(shí)間:2026年02月14日 09:18:34   作者:星河耀銀海  
本文深入解析Java集合框架中的Set接口及其實(shí)現(xiàn)類(lèi),重點(diǎn)內(nèi)容包括Set接口不可重復(fù)性和無(wú)序性的設(shè)計(jì)理念,HashSet基于哈希表的底層實(shí)現(xiàn)與去重機(jī)制,LinkedHashSet維護(hù)插入順序的原理等,需要的朋友可以參考下

一、章節(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):

  1. Set 的去重機(jī)制依賴(lài)元素的 equals() 方法,但為了提高查找效率,會(huì)先通過(guò) hashCode() 方法計(jì)算哈希值,因此重寫(xiě) equals() 方法時(shí)必須重寫(xiě) hashCode() 方法,否則會(huì)導(dǎo)致去重失效
  2. Set 接口沒(méi)有提供基于索引的訪問(wèn)方法(如 get(int index)),因?yàn)槠湓O(shè)計(jì)初衷不強(qiáng)調(diào)元素的順序訪問(wèn)
  3. 所有 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 方法查找流程:

  1. 計(jì)算 o 的 hashCode() 得到哈希值,確定桶位置
  2. 遍歷桶中的元素(鏈表或紅黑樹(shù)),通過(guò) equals() 方法比較是否存在匹配元素
  3. 找到則返回 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))

  1. 鏈地址法(拉鏈法):將同一個(gè)桶中的沖突元素以鏈表形式存儲(chǔ),JDK 8 中當(dāng)鏈表長(zhǎng)度超過(guò) 8 且數(shù)組長(zhǎng)度≥64 時(shí),轉(zhuǎn)為紅黑樹(shù)
  2. 擾動(dòng)函數(shù):對(duì) hashCode() 的返回值進(jìn)行二次哈希計(jì)算(JDK 8 簡(jiǎn)化為一次異或和無(wú)符號(hào)右移),減少哈希沖突的概率
  3. 動(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ě)原則:

  1. 自反性:x.equals(x) 必須返回 true
  2. 對(duì)稱(chēng)性:若 x.equals(y) 為 true,則 y.equals(x) 也必須為 true
  3. 傳遞性:若 x.equals(y) 為 true 且 y.equals(z) 為 true,則 x.equals(z) 必須為 true
  4. 一致性:若 x 和 y 的equals() 比較所依賴(lài)的屬性未變,則 x.equals(y) 的結(jié)果始終不變
  5. 哈希一致性:若 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í)被添加到哈希表和雙向鏈表中:

  1. 哈希表部分:與 HashSet 邏輯一致,通過(guò) hashCode() 和 equals() 保證去重
  2. 雙向鏈表部分:新元素會(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)型HashSetLinkedHashSet
添加 100 萬(wàn)元素89ms102ms
查找單個(gè)元素0ms0ms
遍歷 100 萬(wàn)元素12ms8ms

?? 結(jié)論:LinkedHashSet 的添加操作略慢于 HashSet(因?yàn)樾枰S護(hù)雙向鏈表),但遍歷效率更高;查找和刪除效率與 HashSet 基本一致(均依賴(lài)哈希表)。

4.2.3 訪問(wèn)順序模式(LinkedHashSet 擴(kuò)展特性)

LinkedHashMap 支持兩種順序模式:

  1. 插入順序(默認(rèn)):遍歷順序與元素插入順序一致
  2. 訪問(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)效率):

  1. 每個(gè)節(jié)點(diǎn)要么是紅色,要么是黑色
  2. 根節(jié)點(diǎn)是黑色
  3. 所有葉子節(jié)點(diǎn)(NIL 節(jié)點(diǎn))是黑色
  4. 若一個(gè)節(jié)點(diǎn)是紅色,則其兩個(gè)子節(jié)點(diǎn)都是黑色
  5. 從任意節(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 方法核心流程:

  1. 若紅黑樹(shù)為空,創(chuàng)建根節(jié)點(diǎn)
  2. 若存在比較器,使用比較器的 compare() 方法查找插入位置;否則使用元素的 compareTo() 方法
  3. 若找到相同元素(比較結(jié)果為 0),則替換 value,返回舊 value(TreeSet 認(rèn)為添加失?。?/li>
  4. 若未找到相同元素,創(chuàng)建新節(jié)點(diǎn)并插入紅黑樹(shù)
  5. 調(diào)整紅黑樹(shù)結(jié)構(gòu)(變色、旋轉(zhuǎn)),保證紅黑樹(shù)的平衡特性

紅黑樹(shù)插入調(diào)整示例(以插入節(jié)點(diǎn) 7 為例):

  1. 插入節(jié)點(diǎn) 7 作為紅色節(jié)點(diǎn),發(fā)現(xiàn)父節(jié)點(diǎn) 6 也是紅色,違反紅黑樹(shù)特性 4
  2. 進(jìn)行變色操作:將祖父節(jié)點(diǎn) 5 變?yōu)榧t色,父節(jié)點(diǎn) 6 和叔父節(jié)點(diǎn) 4 變?yōu)楹谏?/li>
  3. 若祖父節(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ì)比表

特性HashSetLinkedHashSetTreeSet
底層數(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),支持以下核心功能:

  1. 為商品添加標(biāo)簽(標(biāo)簽不可重復(fù),需維護(hù)添加順序)
  2. 為商品刪除指定標(biāo)簽
  3. 查詢(xún)商品的所有標(biāo)簽(按添加順序展示)
  4. 查詢(xún)包含指定標(biāo)簽的所有商品
  5. 統(tǒng)計(jì)所有標(biāo)簽的使用頻次(按頻次降序排序)
  6. 批量導(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)的核心特性:

  1. LinkedHashSet:用于商品標(biāo)簽存儲(chǔ),保證去重和插入順序,滿(mǎn)足標(biāo)簽展示需求
  2. HashSet:批量導(dǎo)入標(biāo)簽時(shí)臨時(shí)存儲(chǔ),快速去重
  3. TreeSet:用于標(biāo)簽頻次統(tǒng)計(jì)排序,按頻次降序展示,滿(mǎn)足統(tǒng)計(jì)需求

? 關(guān)鍵技術(shù)亮點(diǎn):

  1. 數(shù)據(jù)安全性:getTags() 方法返回標(biāo)簽集合的副本,防止外部直接修改內(nèi)部數(shù)據(jù)
  2. 高效去重:通過(guò) LinkedHashSet 和 HashSet 實(shí)現(xiàn)不同場(chǎng)景下的快速去重
  3. 靈活排序:使用 TreeSet 自定義比較器,實(shí)現(xiàn)標(biāo)簽頻次的降序排序
  4. 性能優(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)回顧:

  1. HashSet 基于哈希表實(shí)現(xiàn),核心優(yōu)勢(shì)是高效去重(O(1) 平均時(shí)間復(fù)雜度),適用于無(wú)需有序的去重場(chǎng)景
  2. LinkedHashSet 基于哈希表+雙向鏈表實(shí)現(xiàn),兼具去重和插入順序維護(hù)功能,遍歷效率高于 HashSet
  3. TreeSet 基于紅黑樹(shù)實(shí)現(xiàn),核心優(yōu)勢(shì)是排序和導(dǎo)航功能(O(log n) 時(shí)間復(fù)雜度),適用于需要排序和范圍查詢(xún)的場(chǎng)景
  4. Set 集合的去重機(jī)制:HashSet/LinkedHashSet 依賴(lài) hashCode() + equals(),TreeSet 依賴(lài) compareTo()/compare()
  5. 選型核心原則:根據(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)系詳解

    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)消除模板代碼

    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ù)作用

    詳談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é)

    java迭代器基礎(chǔ)知識(shí)點(diǎn)總結(jié)

    在本篇內(nèi)容里小編給大家整理了一篇關(guān)于java迭代器基礎(chǔ)知識(shí)點(diǎn)總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-01-01
  • 使用Spring開(kāi)啟注解AOP的支持放置的位置

    使用Spring開(kāi)啟注解AOP的支持放置的位置

    這篇文章主要介紹了使用Spring開(kāi)啟注解AOP的支持放置的位置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java如何利用LocalDate獲取某個(gè)月的第一天與最后一天日期

    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安裝圖解

    這篇文章主要介紹了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)增刪改查操作

    這篇文章主要介紹了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)路由的方法

    這篇文章主要介紹了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ì)比

    這篇文章主要介紹了詳解mybatis 批量更新數(shù)據(jù)兩種方法效率對(duì)比,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02

最新評(píng)論

铁力市| 福泉市| 兖州市| 克东县| 吴川市| 平安县| 兰州市| 柏乡县| 邮箱| 五大连池市| 邵东县| 黔江区| 资源县| 巩义市| 西平县| 凤城市| 丰原市| 甘德县| 正阳县| 南靖县| 拜泉县| 普安县| 舞钢市| 司法| 长顺县| 和龙市| 泽普县| 长垣县| 千阳县| 威海市| 德江县| 平陆县| 沈丘县| 水富县| 温州市| 弥勒县| 迁西县| 庆云县| 萨迦县| 偏关县| 哈尔滨市|