EnumSet详细讲解
https://blog.csdn.net/tugangkai/article/details/89631886
之前介紹的Set接口的實(shí)現(xiàn)類HashSet/TreeSet,它們內(nèi)部都是用對(duì)應(yīng)的HashMap/TreeMap實(shí)現(xiàn)的,但EnumSet不是,它的實(shí)現(xiàn)與EnumMap沒有任何關(guān)系,而是用極為精簡(jiǎn)和高效的位向量實(shí)現(xiàn)的,位向量是計(jì)算機(jī)程序中解決問題的一種常用方式,我們有必要理解和掌握。
除了實(shí)現(xiàn)機(jī)制,EnumSet的用法也有一些不同。次外,EnumSet可以說是處理枚舉類型數(shù)據(jù)的一把利器,在一些應(yīng)用領(lǐng)域,它非常方便和高效。
下面,我們先來看EnumSet的基本用法,然后通過一個(gè)場(chǎng)景來看EnumSet的應(yīng)用,最后,我們分析EnumSet的實(shí)現(xiàn)機(jī)制。
基本用法
與TreeSet/HashSet不同,EnumSet是一個(gè)抽象類,不能直接通過new新建,也就是說,類似下面代碼是錯(cuò)誤的:
?
EnumSet<Size> set = new EnumSet<Size>();不過,EnumSet提供了若干靜態(tài)工廠方法,可以創(chuàng)建EnumSet類型的對(duì)象,比如:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)noneOf方法會(huì)創(chuàng)建一個(gè)指定枚舉類型的EnumSet,不含任何元素。創(chuàng)建的EnumSet對(duì)象的實(shí)際類型是EnumSet的子類,待會(huì)我們?cè)俜治銎渚唧w實(shí)現(xiàn)。
為方便舉例,我們定義一個(gè)表示星期幾的枚舉類Day,值從周一到周日,如下所示:
?
?可以這么用noneOf方法:
weekend表示休息日,noneOf返回的Set為空,添加了周六和周日,所以輸出為:
[SATURDAY, SUNDAY]EnumSet還有很多其他靜態(tài)工廠方法,如下所示(省略了修飾public static):
可以看到,EnumSet有很多重載形式的of方法,最后一個(gè)接受的的是可變參數(shù),其他重載方法看上去是多余的,之所以有其他重載方法是因?yàn)榭勺儏?shù)的運(yùn)行效率低一些。
應(yīng)用場(chǎng)景
下面,我們通過一個(gè)場(chǎng)景來看EnumSet的應(yīng)用。
想象一個(gè)場(chǎng)景,在一些工作中,比如醫(yī)生、客服,不是每個(gè)工作人員每天都在的,每個(gè)人可工作的時(shí)間是不一樣的,比如張三可能是周一和周三,李四可能是周四和周六,給定每個(gè)人可工作的時(shí)間,我們可能有一些問題需要回答,比如:
- 有沒有哪天一個(gè)人都不會(huì)來?
- 有哪些天至少會(huì)有一個(gè)人來?
- 有哪些天至少會(huì)有兩個(gè)人來?
- 有哪些天所有人都會(huì)來,以便開會(huì)?
- 哪些人周一和周二都會(huì)來??
使用EnumSet,可以方便高效地回答這些問題,怎么做呢?我們先來定義一個(gè)表示工作人員的類Worker,如下所示:
為演示方便,將所有工作人員的信息放到一個(gè)數(shù)組workers中,如下所示:
每個(gè)工作人員的可工作時(shí)間用一個(gè)EnumSet表示。有了這個(gè)信息,我們就可以回答以上的問題了。
哪些天一個(gè)人都不會(huì)來?代碼可以為:
days初始化為所有值,然后遍歷workers,從days中刪除可工作的所有時(shí)間,最終剩下的就是一個(gè)人都不會(huì)來的時(shí)間,這實(shí)際是在求worker時(shí)間并集的補(bǔ)集,輸出為:
[SUNDAY]有哪些天至少會(huì)有一個(gè)人來?就是求worker時(shí)間的并集,代碼可以為:
輸出為:
[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]有哪些天所有人都會(huì)來?就是求worker時(shí)間的交集,代碼可以為:
輸出為:
[TUESDAY]哪些人周一和周二都會(huì)來?使用containsAll方法,代碼可以為:
輸出為:
張三哪些天至少會(huì)有兩個(gè)人來?我們先使用EnumMap統(tǒng)計(jì)每天的人數(shù),然后找出至少有兩個(gè)人的天,代碼可以為:
輸出為:
[TUESDAY, THURSDAY]理解了EnumSet的使用,下面我們來看它是怎么實(shí)現(xiàn)的。
實(shí)現(xiàn)原理
位向量
EnumSet是使用位向量實(shí)現(xiàn)的,什么是位向量呢?就是用一個(gè)位表示一個(gè)元素的狀態(tài),用一組位表示一個(gè)集合的狀態(tài),每個(gè)位對(duì)應(yīng)一個(gè)元素,而狀態(tài)只可能有兩種。
對(duì)于之前的枚舉類Day,它有7個(gè)枚舉值,一個(gè)Day的集合就可以用一個(gè)字節(jié)byte表示,最高位不用,設(shè)為0,最右邊的位對(duì)應(yīng)順序最小的枚舉值,從右到左,每位對(duì)應(yīng)一個(gè)枚舉值,1表示包含該元素,0表示不含該元素。
比如,表示包含Day.MONDAY,Day.TUESDAY,Day.WEDNESDAY,Day.FRIDAY的集合,位向量圖示結(jié)構(gòu)如下:
對(duì)應(yīng)的整數(shù)是23。
位向量能表示的元素個(gè)數(shù)與向量長(zhǎng)度有關(guān),一個(gè)byte類型能表示8個(gè)元素,一個(gè)long類型能表示64個(gè)元素,那EnumSet用的長(zhǎng)度是多少呢?
EnumSet是一個(gè)抽象類,它沒有定義使用的向量長(zhǎng)度,它有兩個(gè)子類,RegularEnumSet和JumboEnumSet。RegularEnumSet使用一個(gè)long類型的變量作為位向量,long類型的位長(zhǎng)度是64,而JumboEnumSet使用一個(gè)long類型的數(shù)組。如果枚舉值個(gè)數(shù)小于等于64,則靜態(tài)工廠方法中創(chuàng)建的就是RegularEnumSet,大于64的話就是JumboEnumSet。
內(nèi)部組成
理解了位向量的基本概念,我們來看EnumSet的實(shí)現(xiàn),同EnumMap一樣,它也有表示類型信息和所有枚舉值的實(shí)例變量,如下所示:
elementType表示類型信息,universe表示枚舉類的所有枚舉值。
EnumSet自身沒有記錄元素個(gè)數(shù)的變量,也沒有位向量,它們是子類維護(hù)的。
對(duì)于RegularEnumSet,它用一個(gè)long類型表示位向量,代碼為:
private long elements = 0L;它沒有定義表示元素個(gè)數(shù)的變量,是實(shí)時(shí)計(jì)算出來的,計(jì)算的代碼是:
對(duì)于JumboEnumSet,它用一個(gè)long數(shù)組表示,有單獨(dú)的size變量,代碼為:
靜態(tài)工廠方法
我們來看EnumSet的靜態(tài)工廠方法noneOf,代碼為:
getUniverse的代碼與上節(jié)介紹的EnumMap是一樣的,就不贅述了。如果元素個(gè)數(shù)不超過64,就創(chuàng)建RegularEnumSet,否則創(chuàng)建JumboEnumSet。
RegularEnumSet和JumboEnumSet的構(gòu)造方法為:
它們都調(diào)用了父類EnumSet的構(gòu)造方法,其代碼為:
就是給實(shí)例變量賦值,JumboEnumSet根據(jù)元素個(gè)數(shù)分配足夠長(zhǎng)度的long數(shù)組。
其他工廠方法基本都是先調(diào)用noneOf構(gòu)造一個(gè)空的集合,然后再調(diào)用添加方法,我們來看添加方法。
添加元素
RegularEnumSet的add方法的代碼為:
主要代碼是按位或操作:
elements |= (1L << ((Enum)e).ordinal());(1L << ((Enum)e).ordinal())將元素e對(duì)應(yīng)的位設(shè)為1,與現(xiàn)有的位向量elements相或,就表示添加e了。從集合論的觀點(diǎn)來看,這就是求集合的并集。
JumboEnumSet的add方法的代碼為:
?
與RegularEnumSet的add方法的區(qū)別是,它先找對(duì)應(yīng)的數(shù)組位置,eOrdinal >>> 6就是eOrdinal除以64,eWordNum就表示數(shù)組索引,有了索引之后,其他操作與RegularEnumSet就類似了。
對(duì)于其他操作,JumboEnumSet的思路是類似的,主要算法與RegularEnumSet一樣,主要是增加了尋找對(duì)應(yīng)long位向量的操作,或者有一些循環(huán)處理,邏輯也都比較簡(jiǎn)單,后文就只介紹RegularEnumSet的實(shí)現(xiàn)了。
RegularEnumSet的addAll方法的代碼為:
類型正確的話,就是按位或操作。
刪除元素
remove方法的代碼為:
主要代碼是:
elements &= ~(1L << ((Enum)e).ordinal());~是取反,該代碼將元素e對(duì)應(yīng)的位設(shè)為了0,這樣就完成了刪除。
從集合論的觀點(diǎn)來看,remove就是求集合的差,A-B等價(jià)于A∩B’,B’表示B的補(bǔ)集。代碼中,elements相當(dāng)于A,(1L << ((Enum)e).ordinal())相當(dāng)于B,~(1L << ((Enum)e).ordinal())相當(dāng)于B’,elements &= ~(1L << ((Enum)e).ordinal())就相當(dāng)于A∩B’,即A-B。
查看是否包含某元素
contains方法的代碼為:
代碼也很簡(jiǎn)單,按位與操作,不為0,則表示包含。
查看是否包含集合中的所有元素
containsAll方法的代碼為:
最后的位操作有點(diǎn)晦澀。我們從集合論的角度解釋下,containsAll就是在檢查參數(shù)c表示的集合是不是當(dāng)前集合的子集。一般而言,集合B是集合A的子集,即B?A,等價(jià)于A’∩B是空集?,A’表示A的補(bǔ)集,如下圖所示:
?
上面代碼中,elements相當(dāng)于A,es.elements相當(dāng)于B,~elements相當(dāng)于求A的補(bǔ)集,(es.elements & ~elements) == 0;就是在驗(yàn)證A’∩B是不是空集,即B是不是A的子集。
只保留參數(shù)集合中有的元素
retainAll方法的代碼為:
從集合論的觀點(diǎn)來看,這就是求集合的交集,所以主要代碼就是按位與操作,容易理解。
求補(bǔ)集
EnumSet的靜態(tài)工廠方法complementOf是求補(bǔ)集,它調(diào)用的代碼是:
這段代碼也有點(diǎn)晦澀,elements=~elements比較容易理解,就是按位取反,相當(dāng)于就是取補(bǔ)集,但我們知道elements是64位的,當(dāng)前枚舉類可能沒有用那么多位,取反后高位部分都變?yōu)榱?,需要將超出universe.length的部分設(shè)為0。下面代碼就是在做這件事:
elements &= -1L-1L是64位全1的二進(jìn)制,我們?cè)谄饰鯥nteger一節(jié)介紹過移動(dòng)位數(shù)是負(fù)數(shù)的情況,上面代碼相當(dāng)于:
elements &= -1L如果universe.length為7,則-1L>>>(64-7)就是二進(jìn)制的1111111,與elements相與,就會(huì)將超出universe.length部分的右邊的57位都變?yōu)?。
實(shí)現(xiàn)原理小結(jié)
以上就是EnumSet的基本實(shí)現(xiàn)原理,內(nèi)部使用位向量,表示很簡(jiǎn)潔,節(jié)省空間,大部分操作都是按位運(yùn)算,效率極高。
小結(jié)
本節(jié)介紹了EnumSet的用法和實(shí)現(xiàn)原理,用法上,它是處理枚舉類型數(shù)據(jù)的一把利器,簡(jiǎn)潔方便,實(shí)現(xiàn)原理上,它使用位向量,精簡(jiǎn)高效。
對(duì)于只有兩種狀態(tài),且需要進(jìn)行集合運(yùn)算的數(shù)據(jù),使用位向量進(jìn)行表示、位運(yùn)算進(jìn)行處理,是計(jì)算機(jī)程序中一種常用的思維方式。
至此,關(guān)于具體的容器類,我們就介紹完了。Java容器類中還有一些過時(shí)的容器類,以及一些不常用的類,我們就不介紹了。
在介紹具體容器類的過程中,我們忽略了一個(gè)實(shí)現(xiàn)細(xì)節(jié),那就是,所有容器類其實(shí)都不是從頭構(gòu)建的,它們都繼承了一些抽象容器類。這些抽象類提供了容器接口的部分實(shí)現(xiàn),方便了Java具體容器類的實(shí)現(xiàn)。如果我們需要實(shí)現(xiàn)自定義的容器類,也應(yīng)該考慮從這些抽象類繼承。
那,具體都有什么抽象類?它們都提供了哪些基礎(chǔ)功能?如何進(jìn)行擴(kuò)展呢?讓我們下節(jié)來探討。
?
轉(zhuǎn)載于:https://www.cnblogs.com/hyl8218/p/11271317.html
總結(jié)
以上是生活随笔為你收集整理的EnumSet详细讲解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日期处理工具类
- 下一篇: Myeclipse 2014配置SVN详