Java筆記(九)EnumMap & EnumSet
阿新 • • 發佈:2018-11-28
EnumMap & EnumSet
一、EnumMap
一)基本用法
public static Map<Size, Integer> countBySize(List<Clothes> clothes) { EnumMap<Size, Integer> map = new EnumMap<>(Size.class); for (Clothes c : clothes) { Size size = c.getSize(); Integer count= map.get(size); if (count != null) { map.put(size, count + 1); } else { map.put(size, 1); } } return map; } public static void main(String[] args) { List<Clothes> clothes = Arrays.asList(new Clothes[]{new Clothes("C001",Size.SMALL), new Clothes("C002", Size.LARGE), new Clothes("C003", Size.LARGE), new Clothes("C004", Size.MEDIUM), new Clothes("C005", Size.SMALL), new Clothes("C006", Size.SMALL), }); System.out.println(countBySize(clothes)); //{SMALL=3, MEDIUM=1, LARGE=2}}
EnumMap是保證順序的:輸出是按照鍵在列舉中的順序。
二)實現原理
EnumMap的例項變數:
private final Class<K> keyType; //型別資訊 private transient K[] keyUniverse; //表示鍵,是所有可能的列舉值 private transient Object[] vals; //表示鍵對應的值 private transient int size = 0; //表示鍵值對個數
基本構造方法:
public EnumMap(Class<K> keyType) { this.keyType = keyType; keyUniverse = getKeyUniverse(keyType); //最終呼叫了列舉型別的values方法,該方法返回所有可能的列舉值 vals = new Object[keyUniverse.length]; }
儲存鍵值對的put方法:
public V put(K key, V value) { //檢查鍵的型別 typeCheck(key); //獲取索引 int index = key.ordinal(); //放入值value Object oldValue = vals[index]; //如果有值為null打包null值,這是為了區別null值與沒有值 vals[index] = maskNull(value); if(oldValue == null) size++; return unmaskNull(oldValue); }
private static final Object NULL = new Object() { public int hashCode() { return 0; } public String toString() { return "java.util.EnumMap.NULL"; } }; private Object maskNull(Object value) { return (value == null ? NULL : value); } private V unmaskNull(Object value) { return(V) (value == NULL ? null : value); }
二、EnumSet
EnumSet的實現與EnumMap沒有任何關係,而是用極為精簡高效的位向量實現的。
位向量是計算機程式中解決問題的一種常用方式,我們有必要理解和掌握。
一)基本用法
通過EnumSet的靜態工廠方法,可以建立EnumSet物件,比如:
//建立一個指定型別的EnumSet,不含任何元素,建立的EnumSet實際型別是EnumSet的子類 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
用法舉例:
public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
EnumSet<Day> days = EnumSet.noneOf(Day.class); days.add(Day.FRIDAY); days.add(Day.SUNDAY); days.add(Day.MONDAY); System.out.println(days); //[MONDAY, FRIDAY, SUNDAY]
EnumSet的其他靜態工廠方法:
//初始集合包括指定列舉型別的所有列舉值 <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) //初始集合包括列舉值中指定範圍的元素 <E extends Enum<E>> EnumSet<E> range(E from, E to) //包含指定集合的補集 <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) //包含引數中的所有元素 <E extends Enum<E>> EnumSet<E> of(E e) <E extends Enum<E>> EnumSet<E> of(E e1, E e2) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5) <E extends Enum<E>> EnumSet<E> of(E first, E... rest) //包含引數中的所有元素 <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
二、應用場景
Worker[] workers = new Worker[]{ new Worker("Tom", EnumSet.of(Day.FRIDAY, Day.MONDAY, Day.SATURDAY)), new Worker("Jerry", EnumSet.of(Day.MONDAY, Day.WEDNESDAY, Day.THURSDAY)), new Worker("Teddy", EnumSet.of(Day.TUESDAY, Day.FRIDAY, Day.WEDNESDAY)) }; //哪些天一個人都不會來 EnumSet<Day> allDays = EnumSet.allOf(Day.class); for (Worker w : workers) { allDays.remove(w.getWorkingDays()); }
三、實現原理
位向量:用一個位表示一個元素狀態,用一組位表示集合狀態,
每個位對應一個元素,而狀態只可能有兩種。
比如之前的列舉類Day,它有7個列舉值,一個Day的集合就可以用一個位元組byte表示,
最高位不用設為0,從右到左,每位對應一個列舉值,1表示包含該元素,0表示不包含該元素。
EnumSet的實現,首先是主要例項變數:
final Class<E> elementType; //表示型別資訊 final Enum[] universe; //表示所有列舉值
EnumSet自身沒有記錄元素個數的變數,也沒有位向量,它們是子類維護的。
對於RegularEnumSet,它用一個long型別表示位向量,程式碼為:
private long elements = 0L;
RegularEnumSet沒有定義元素個數的變數,都是實時計算出來的:
public int size() { return Long.bitCount(elements); }
而對於JumboEnumSet,用一個long陣列表示,有單獨的size變數
private long elements[]; private int size = 0;
EnumSet的靜態工廠方法:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
RegularEnum 和 SetJumboEnumSet的構造方法:
RegularEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); } JumboEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); elements = new long[(universe.length + 63) >>> 6]; }
EnumSet(Class<E>elementType, Enum[] universe) { this.elementType = elementType; this.universe = universe; }
其他原始碼略。
四、實現原理
對於只有兩種狀態,且需要進行集合運算的資料,使用位向量表示
、位運算進行處理,是計算機程式中一種常見的思維方式。