Java1.8-RegularEnumSet和JumboEnumSet源碼解析

RegularEnumSet概述

??EnumSet畢竟只是個抽象類谷誓,我們在使用的時候都是使用它的兩個實現(xiàn)類:RegularEnumSet,JumboEnumSet吨凑。其實在說EnumSet的noneof方法的時候已經(jīng)說過捍歪,當(dāng)EnumSet的容量大于64的時候,創(chuàng)建的是JumboEnumSet鸵钝,否則創(chuàng)建的是RegularEnumSet糙臼。

??因為具體的操作(比如add操作)畢竟是在實現(xiàn)類中的,所以我們現(xiàn)在先來分析一下RegularEnumSet這個類恩商。

屬性
/**
 * Private implementation class for EnumSet, for "regular sized" enum types
 * (i.e., those with 64 or fewer enum constants).
 */
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    // 使用位向量保存
    private long elements = 0L;
    // 構(gòu)造方法也是調(diào)用抽象類的構(gòu)造方法來實現(xiàn)
    RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
        super(elementType, universe);
    }
}

從文檔及結(jié)構(gòu)上我們可以得出幾點結(jié)論:

  1. RegularEnumSet是枚舉類型的私有實現(xiàn)類变逃,我們無法直接調(diào)用,我們只能使用EnumSet怠堪,而EnumSet則會根據(jù)length相應(yīng)的調(diào)用RegularEnumSet實現(xiàn)類揽乱;
  2. RegularEnumSet保存數(shù)據(jù)和常規(guī)的Set不同名眉,RegularEnumSet中只有一個long類型的elements變量,這是因為保存的時候保存的并不是實際的元素凰棉,而是保存的是bit损拢,0和1;

方法

add方法
/**
 * 如果元素不存在撒犀,添加
 */
public boolean add(E e) {
    // 校驗枚舉類型
    typeCheck(e);

    long oldElements = elements;
    elements |= (1L << ((Enum<?>)e).ordinal());
    return elements != oldElements;
}

/**
 * 用于校驗枚舉類型福压,位于EnumSet中
 */
final void typeCheck(E e) {
    Class<?> eClass = e.getClass();
    if (eClass != elementType && eClass.getSuperclass() != elementType)
        throw new ClassCastException(eClass + " != " + elementType);
}

add方法中的位運算其實很有意思,首先或舞,每一個枚舉元素都有一個屬性ordinal荆姆,用來表示該元素在枚舉類型中的次序或者說下標(biāo)。


add過程

??add之后映凳,elements二進制對應(yīng)的ordinal位設(shè)置為了1胆筒。其實,add操作就是設(shè)置long類型的elements對應(yīng)下標(biāo)位置的值是0或者是1诈豌,也就是將每一個枚舉元素在elements的二進制中占用一位腐泻。因為long是64位,所以RegularEnumSet的長度自然是不能大于64的队询。
??還有一點派桩,就是判斷元素是否添加成功,直接通過判斷添加前后elements的值有沒有變化來判斷蚌斩。

其實铆惑,明白了這點之后,后面的許多操作都很簡單了送膳。

size方法
public int size() {
        return Long.bitCount(elements);
    }

其實size方法的實現(xiàn)是通過Long類型的bitCount來實現(xiàn)的员魏,就是統(tǒng)計long類型二進制中1的個數(shù)。

addAll方法
void addAll() {
    if (universe.length != 0)
        elements = -1L >>> -universe.length;
}

??addAll方法就是將elements上叠聋,從低位到枚舉長度上的下標(biāo)值置為1撕阎。比如某一個枚舉類型共5個元素,而addAll就是將elements的二進制的低5位置為1碌补。

??addAll方法這里涉及到了無符號右移的操作虏束,其實,這個操作也挺有意思厦章,但具體的實現(xiàn)我把它放到另一篇專門介紹位運算符的文章里了镇匀。鏈接:Java位運算學(xué)習(xí)

addRange方法
void addRange(E from, E to) {
    elements = (-1L >>>  (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
}

該方法是添加枚舉中某一段范圍內(nèi)的元素。這個方法同樣設(shè)計的也很精巧袜啃,先右無符號位移汗侵,將最低位置為0,然后左移對應(yīng)的位置即可。

complement方法
void complement() {
    if (universe.length != 0) {
        elements = ~elements;
        elements &= -1L >>> -universe.length;  // Mask unused bits
    }
}

這個方法其實和上面類似晰韵,只不過多了一步按位非的操作发乔。
其他方法其實都是和上面這幾個方法大差不差,只要明白了位運算雪猪,基本上這些方法都可以很快理解的栏尚。

最后,再說一個很有意思的方法:EnumSetIterator的next方法

private class EnumSetIterator<E extends Enum<E>> implements Iterator<E> {
    /**
     *  elements的值
     */
    long unseen;

    /**
     * elements二進制對應(yīng)的1的位置
     */
    long lastReturned = 0;

    EnumSetIterator() {
        unseen = elements;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (unseen == 0)
            throw new NoSuchElementException();
        lastReturned = unseen & -unseen;
        unseen -= lastReturned;
        return (E) universe[Long.numberOfTrailingZeros(lastReturned)];
    }
}

??首先浪蹂,lastReturned = unseen & -unseen; 計算的是unseen的二進制最低位第一個非0位代表的十進制數(shù)抵栈。如果unseen是0111告材,那返回的就是0001坤次,十進制是1,如果unseen是0100斥赋,那返回的就是0100缰猴,十進制是4。
??而 Long.numberOfTrailingZeros(lastReturned) 計算的是lastReturned從最低位開始疤剑,第一位為1的下標(biāo)值(或者換一種說法滑绒,就是從最低位開始,到第一位為1這中間0的個數(shù))隘膘。比如lastReturned是4疑故,二進制是0100,那這里返回的就是2弯菊;如果lastReturned是0101纵势,那這里返回的就是0。
??依稀記得leetcode中有一個算法就是計算 尾部的零的個數(shù)(Trailing Zeros)管钳。

這里钦铁,其實只要過一遍流程基本就清楚了。

總結(jié)

??其實RegularEnumSet中進行的操作就是圍繞長整型elements的二進制位上的1和0進行的才漆。添加元素牛曹,設(shè)置為1,刪除元素醇滥,設(shè)置為0黎比,清空,直接將該長整型置為0鸳玩。

JumboEnumSet概述

??當(dāng)枚舉元素的個數(shù)超過了64之后焰手,就將使用JumboEnumSet來進行操作。其實JumboEnumSet中大部分操作和RegularEnumSet都差不多怀喉,有一點不太一樣的就是JumboEnumSet里的elements是個long類型的數(shù)組书妻。

private long elements[];

所以,JumboEnumSet中有一步操作就是定位到數(shù)組中對應(yīng)的long元素上。

構(gòu)造方法
JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
    super(elementType, universe);
    elements = new long[(universe.length + 63) >>> 6];
}

這里 new long[(universe.length + 63) >>> 6];躲履,無符號右移可以大致認(rèn)為除以64见间,計算的就是數(shù)組的容量。

我們再隨便拿一個方法說一下:

addAll方法
void addAll() {
    for (int i = 0; i < elements.length; i++)
        elements[i] = -1;
    elements[elements.length - 1] >>>= -universe.length;
    size = universe.length;
}

??這里處理的很巧妙工猜。首先米诉,循環(huán)設(shè)置數(shù)組里的long是-1,-1的二進制是1111....1111篷帅,所以 elements[elements.length - 1] >>>= -universe.length; 這一步史侣,就是計算long數(shù)組中最后一個long元素二進制位上的1和0;

比如說魏身,枚舉元素是68個惊橱,那么elements數(shù)組的第一個long元素已經(jīng)在循環(huán)的時候設(shè)置為了-1,也就是1111....1111箭昵,進行這一步進行的就是將第二個long元素進行位移運算税朴,結(jié)果為0000....0000 1111。

其他的就沒什么說的了家制,總之正林,只要明白了位運算,這些方法還是挺簡單的颤殴。

本文參考自:
JDK源碼解讀之RegularEnumSet

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末觅廓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涵但,更是在濱河造成了極大的恐慌杈绸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贤笆,死亡現(xiàn)場離奇詭異蝇棉,居然都是意外死亡,警方通過查閱死者的電腦和手機芥永,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門篡殷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人埋涧,你說我怎么就攤上這事板辽。” “怎么了棘催?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵劲弦,是天一觀的道長。 經(jīng)常有香客問我醇坝,道長邑跪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮画畅,結(jié)果婚禮上砸琅,老公的妹妹穿的比我還像新娘。我一直安慰自己轴踱,他們只是感情好症脂,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淫僻,像睡著了一般诱篷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雳灵,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天棕所,我揣著相機與錄音,去河邊找鬼细办。 笑死橙凳,一個胖子當(dāng)著我的面吹牛蕾殴,可吹牛的內(nèi)容都是我干的笑撞。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼钓觉,長吁一口氣:“原來是場噩夢啊……” “哼茴肥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荡灾,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤瓤狐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后批幌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體础锐,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年荧缘,在試婚紗的時候發(fā)現(xiàn)自己被綠了皆警。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡截粗,死狀恐怖信姓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绸罗,我是刑警寧澤意推,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站珊蟀,受9級特大地震影響菊值,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一腻窒、第九天 我趴在偏房一處隱蔽的房頂上張望略步。 院中可真熱鬧,春花似錦定页、人聲如沸趟薄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杭煎。三九已至,卻和暖如春卒落,著一層夾襖步出監(jiān)牢的瞬間羡铲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工儡毕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留也切,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓腰湾,卻偏偏與公主長得像雷恃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子费坊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350