問:簡單談?wù)勀銓?EnumMap 的理解及其特點與應(yīng)用場景?
答:EnumMap 是對 Map 接口的實現(xiàn)類,其 key-value 映射中的 key 是 Enum 類型,其原理是一個對象數(shù)組,數(shù)組的下標(biāo)索引就是根據(jù) Map 中的 key 直接獲取(即枚舉中的 ordinal 值),數(shù)組長度就是枚舉類成員個數(shù);當(dāng) key 為枚舉類型時其效率比 HashMap 高,因為可以直接獲取數(shù)組下標(biāo)索引訪問到元素;此外 EnumMap 是保證順序的,輸出是按照 key 在枚舉中的順序來確定的。
大多數(shù)時候使用 EnumMap 的場景都是 key-value 對存儲中 key 為枚舉類型的場景,不過需要注意 EnumMap 的構(gòu)造方法,如下:
//需要傳遞一個類型信息,因為沒有這個類信息就不知道具體的枚舉類是什么,
// 也就無法初始化內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。
public EnumMap(Class < K > keyType)
// 其他構(gòu)造方法。
public EnumMap(EnumMap < K, ? extends V > m )
public EnumMap(Map < K, ? extends V > m )
所以 EnumMap 與 HashMap 的主要不同就是構(gòu)造方法需要傳遞類型參數(shù),此外 EnumMap 能依據(jù) key 的枚舉順序保證有序性,當(dāng) key 為枚舉類型時使用 EnumMap 的效率遠遠高于 HashMap。
問:簡單說說 EnumMap 的實現(xiàn)原理?
答:EnumMap 的實現(xiàn)原理依賴內(nèi)部兩個長度相同的數(shù)組,一個表示所有可能的鍵,一個表示對應(yīng)的值,當(dāng)放入 key-value 時首先會檢查鍵的類型,如果類型不對會拋出異常,否則調(diào)用 key 的 ordinal 獲取索引 index,并將值 value 放入值數(shù)組 vals[index] 中(注意:如果值 value 為 null,則為了區(qū)別真正的 null 與沒有值,EnumMap 會將 null 值包裝成一個特殊的對象)。
其構(gòu)造方法主要就是在初始化相關(guān)數(shù)組,如下:
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable {
//key鍵的具體枚舉類型
private final Class<K> keyType;
// key的所有枚舉值
private transient K[] keyUniverse;
// EnumMap的存儲實現(xiàn),僅僅為一個枚舉成員個數(shù)長度的數(shù)組
private transient Object[] vals;
......
// 構(gòu)造方法
public EnumMap(Class<K> keyType) {
// key的枚舉類型賦值
this.keyType = keyType;
// 獲取枚舉類的所有枚舉值存入數(shù)組緩存使用
keyUniverse = getKeyUniverse(keyType);
// 實例化枚舉值個數(shù)長度的數(shù)組
vals = new Object[keyUniverse.length];
}
......
}
其 put 操作的源碼如下:
public V put (K key, V value )
{
//檢查key類型是不是構(gòu)造時的類型
typeCheck(key);
// 獲取枚舉成員在枚舉中的順序位置
int index = key.ordinal();
// 放值或者替換 //(注意:這里如果value為null這會進行包裝為Object和解包裝操作)
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null) size++;
return unmaskNull(oldValue);
}
// value為null時包裝成重寫過toString和hashCode方法的Object
private Object maskNull (Object value ){
return (value == null ? NULL : value);
}
// value為null時從重寫過toString和hashCode方法的Object解包裝為null
private V unmaskNull (Object value ){
return (V) (value == NULL ? null : value);
}
其獲取指定 key 的 value 的 get 方法實現(xiàn)如下:
//注意這里如果value為null這會進解包裝操作,參見put
public V get (Object key ){
return (isValidKey(key) ? unmaskNull(vals[((Enum) key).ordinal()]) : null);
}