首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

計算機程序的思維邏輯 (51):EnumSet 位向量實現原理與高效枚舉集合操作實戰

     編輯:Java 綜合教學

前言:為什麼專門為枚舉設計 EnumSet?

繼上一節 EnumMap 後,本節探討 EnumSet——專為枚舉類型設計的 Set 實現。不同於 HashSet (基於 HashMap) 和 TreeSet (基於 TreeMap),EnumSet 使用位向量實現,極致壓縮空間、極速位運算,是處理枚舉集合的「利器」。本文將覆蓋:基本用法、實戰場景、位向量原理、源碼級實現分析。

基本用法:靜態工廠方法創建

EnumSet 是抽象類,無公共構造器,通過靜態工廠方法獲取實例:

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

// 1. 空集合
Set<Day> weekend = EnumSet.noneOf(Day.class);
weekend.add(Day.SATURDAY);
weekend.add(Day.SUNDAY); // [SATURDAY, SUNDAY]

// 2. 全集合
Set<Day> allDays = EnumSet.allOf(Day.class); // 7 個元素

// 3. 範圍集合 (包含端點)
Set<Day> workDays = EnumSet.range(Day.MONDAY, Day.FRIDAY); // Mon-Fri

// 4. 補集
Set<Day> nonWorkDays = EnumSet.complementOf(workDays); // Sat, Sun

// 5. 顯式元素 (多重載優於可變參數性能)
Set<Day> mwf = EnumSet.of(Day.MONDAY, Day.WEDNESDAY, Day.FRIDAY);

// 6. 複製
Set<Day> copy = EnumSet.copyOf(mwf);

性能提示:of(E...) 可變參數會創建數組,of(E, E) 等固定重載避免數組分配,枚舉元素少時優先用固定重載。

實戰場景:醫生排班問題

問題:張三 (Mon, Tue, Wed, Fri)、李四 (Tue, Thu, Sat)、王五 (Tue, Thu)。回答:

  1. 哪天無人值班?
  2. 哪天至少一人值班?
  3. 哪天全員值班?
  4. 誰周一周二都在?
  5. 哪天至少兩人值班?

領域建模

class Worker {
    String name;
    Set<Day> availableDays; // EnumSet
    // constructor, getters
}

Worker[] workers = {
    new Worker("張三", EnumSet.of(Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, Day.FRIDAY)),
    new Worker("李四", EnumSet.of(Day.TUESDAY, Day.THURSDAY, Day.SATURDAY)),
    new Worker("王五", EnumSet.of(Day.TUESDAY, Day.THURSDAY))
};

集合運算一行解決

// 1. 無人值班 = 全集合 - 並集
Set<Day> none = EnumSet.allOf(Day.class);
for (Worker w : workers) none.removeAll(w.getAvailableDays());
// [SUNDAY]

// 2. 至少一人 = 並集
Set<Day> atLeastOne = EnumSet.noneOf(Day.class);
for (Worker w : workers) atLeastOne.addAll(w.getAvailableDays());
// [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]

// 3. 全員值班 = 交集
Set<Day> all = EnumSet.allOf(Day.class);
for (Worker w : workers) all.retainAll(w.getAvailableDays());
// [TUESDAY]

// 4. 周一周二都在 = containsAll
Set<Worker> mwfWorkers = new HashSet<>();
Set<Day> mwf = EnumSet.of(Day.MONDAY, Day.TUESDAY);
for (Worker w : workers) 
    if (w.getAvailableDays().containsAll(mwf)) mwfWorkers.add(w);
// [張三]

// 5. 至少兩人 = 統計頻次 (EnumMap 輔助)
Map<Day, Integer> count = new EnumMap<>(Day.class);
for (Worker w : workers)
    for (Day d : w.getAvailableDays())
        count.merge(d, 1, Integer::sum);

Set<Day> atLeastTwo = EnumSet.noneOf(Day.class);
for (Map.Entry<Day, Integer> e : count.entrySet())
    if (e.getValue() >= 2) atLeastTwo.add(e.getKey());
// [TUESDAY, THURSDAY]

核心洞察:EnumSet 將「集合運算」映射為「位運算」,addAll=或、retainAll=與、removeAll=與非、complementOf=非,代碼極其簡潔高效。

位向量原理:一個 long 搞定 64 個枚舉值

位向量結構

枚舉值按聲明順序映射到 bit 位:

Day.MONDAY    → bit 0 (最低位)
Day.TUESDAY   → bit 1
...
Day.SUNDAY    → bit 6
bit 7+        → 未使用 (置 0)

集合 {MONDAY, TUESDAY, WEDNESDAY, FRIDAY} 
= 0b00010111 = 0x17 = 23 (十進制)

位向量示意圖

兩個子類實現

類別存儲結構適用枚舉數量
RegularEnumSetlong elements (單個 64 位)≤ 64
JumboEnumSetlong[] elements (數組)> 64

工廠方法 noneOf 根據 universe.length 決定實例化哪個子類。

源碼級核心操作實現 (RegularEnumSet)

添加元素:位或運算

public boolean add(E e) {
    typeCheck(e);
    long old = elements;
    elements |= (1L << ((Enum)e).ordinal()); // 設置對應 bit 為 1
    return elements != old;
}

刪除元素:位與非運算

public boolean remove(Object e) {
    if (e == null) return false;
    // 類型檢查...
    long old = elements;
    elements &= ~(1L << ((Enum)e).ordinal()); // 設置對應 bit 為 0
    return elements != old;
}

集合論視角:A - B = A ∩ B',~(1L<<ordinal) 即 B'。

包含判斷:位與運算

public boolean contains(Object e) {
    // 類型檢查...
    return (elements & (1L << ((Enum)e).ordinal())) != 0;
}

包含所有:子集判斷 (A' ∩ B = ∅)

public boolean containsAll(Collection<?> c) {
    if (!(c instanceof RegularEnumSet)) return super.containsAll(c);
    RegularEnumSet es = (RegularEnumSet) c;
    if (es.elementType != elementType) return es.isEmpty();
    return (es.elements & ~elements) == 0; // es 中有元素不在 this 中 → 非子集
}

保留交集:位與運算

public boolean retainAll(Collection<?> c) {
    if (!(c instanceof RegularEnumSet)) return super.retainAll(c);
    RegularEnumSet es = (RegularEnumSet) c;
    if (es.elementType != elementType) { elements = 0; return true; }
    long old = elements;
    elements &= es.elements; // 交集
    return elements != old;
}

補集操作:位非 + 掩碼

void complement() {
    if (universe.length != 0) {
        elements = ~elements; // 全部取反
        // 掩碼:保留低 universe.length 位,高位清 0
        // -1L >>> -universe.length 等價於 (1L << universe.length) - 1
        elements &= -1L >>> -universe.length; 
    }
}

移位陷阱:Java 移位操作只取右操作數低 6 位 (long 取低 6 位即模 64)。-universe.length 為負數,補碼高位全 1,>>> 無符號右移,實際等價於 64 - universe.length 位右移。

JumboEnumSet 差異:數組索引 + 位運算

// 添加元素
int eOrdinal = e.ordinal();
int eWordNum = eOrdinal >>> 6; // / 64
long old = elements[eWordNum];
elements[eWordNum] |= (1L << eOrdinal); // 對應 long 內部位運算
// size++ 維護

其他操作邏輯同 RegularEnumSet,只是多一層數組索引定位。

總結:位向量思維的通用價值

EnumSet 展示了計算機科學中經典的位向量模式:

  • 適用條件:元素域有限、固定、需頻繁集合運算 (並/交/差/補)
  • 優勢:O(1) 空間 (固定長度)、O(1) 時間 (單指令位運算)、緩存友好
  • 應用場景:權限掩碼、標籤系統、特徵向量、布隆過濾器基礎、狀態機編碼

掌握位向量思維,能在處理「有限狀態集合」問題時,跳出「對象集合」思維定勢,寫出極致高效的代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved