電腦程式的思維邏輯 (50),思維邏輯

來源:互聯網
上載者:User

電腦程式的思維邏輯 (50),思維邏輯

上節我們提到,如果需要一個Map的實作類別,並且鍵的類型為枚舉類型,可以使用HashMap,但應該使用一個專門的實作類別EnumMap。

為什麼要有一個專門的類呢?我們之前介紹過枚舉的本質,主要是因為枚舉類型有兩個特徵,一是它可能的值是有限的且預先定義的,二是枚舉值都有一個順序,這兩個特徵使得可以更為高效的實現Map介面。

我們先來看EnumMap的用法,然後看它到底是怎麼實現的。

用法

舉個簡單的例子,比如,有一批關於衣服的記錄,我們希望按尺寸統計衣服的數量。

定義一個簡單的枚舉類,Size,表示衣服的尺寸:

public enum Size {    SMALL, MEDIUM, LARGE}

定義一個簡單類,Clothes,表示衣服:

class Clothes {    String id;    Size size;        public Clothes(String id, Size size) {        this.id = id;        this.size = size;    }    public String getId() {        return id;    }    public Size getSize() {        return size;    }}

有一個表示衣服記錄的列表List<Clothes>,我們希望按尺寸統計數量,統計方法可以為:

public static Map<Size, Integer> countBySize(List<Clothes> clothes){    Map<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;}

大部分代碼都很簡單,需要注意的是EnumMap的構造方法,如下所示:

Map<Size, Integer> map = new EnumMap<>(Size.class);

與HashMap不同,它需要傳遞一個類型資訊,我們在37節簡單介紹過運行時類型資訊,Size.class表示枚舉類Size的運行時類型資訊,Size.class也是一個對象,它的類型是Class。

為什麼需要這個參數呢?沒有這個,EnumMap就不知道具體的枚舉類是什麼,也無法初始化內部的資料結構。

使用以上的統計方法也是很簡單的,比如:

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還有兩個構造方法,可以接受一個索引值匹配的EnumMap或普通Map,如下所示:

public EnumMap(EnumMap<K, ? extends V> m)public EnumMap(Map<K, ? extends V> m)

比如:

Map<Size,Integer> hashMap = new HashMap<>();hashMap.put(Size.LARGE, 2);hashMap.put(Size.SMALL, 1);Map<Size, Integer> enumMap = new EnumMap<>(hashMap);

以上就是EnumMap的基本用法,與HashMap的主要不同,一是構造方法需要傳遞型別參數,二是保證順序。

有人可能認為,對於枚舉,使用Map是沒有必要的,比如對於上面的統計例子,可以使用一個簡單的數組:

public static int[] countBySize(List<Clothes> clothes){    int[] stat = new int[Size.values().length];    for(Clothes c : clothes){        Size size = c.getSize();        stat[size.ordinal()]++;    }    return stat;}

這個方法可以這麼使用:

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),});int[] stat = countBySize(clothes);for(int i=0; i<stat.length; i++){    System.out.println(Size.values()[i]+": "+ stat[i]);}

輸出為:

SMALL 3MEDIUM 1LARGE 2

可以達到同樣的目的。但,直接使用數組需要自己維護數組索引和枚舉值之間的關係,正如枚舉的優點是簡潔、安全、方便一樣,EnumMap同樣是更為簡潔、安全、方便,它內部也是基於數組實現的,但隱藏了細節,提供了更為方便安全的介面。

實現原理

下面我們來看下具體的代碼,從內部組成開始。

內部組成

EnumMap有如下執行個體變數:

private final Class<K> keyType;private transient K[] keyUniverse;private transient Object[] vals;private transient int size = 0;

keyType表示類型資訊,keyUniverse表示鍵,是所有可能的枚舉值,vals表示鍵對應的值,size表示索引值對個數。

構造方法

EnumMap的基本構造方法代碼為:

public EnumMap(Class<K> keyType) {    this.keyType = keyType;    keyUniverse = getKeyUniverse(keyType);    vals = new Object[keyUniverse.length];}

調用了getKeyUniverse以初始化鍵數組,其代碼為:

private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {    return SharedSecrets.getJavaLangAccess()                                    .getEnumConstantsShared(keyType);}

這段代碼又調用了其他一些比較底層的代碼,就不列舉了,原理是最終調用了枚舉類型的values方法,values方法返回所有可能的枚舉值。關於values方法,我們在枚舉的本質一節介紹過其用法和實現原理,這裡就不贅述了。

儲存索引值對

put方法的代碼為:

public V put(K key, V value) {    typeCheck(key);    int index = key.ordinal();    Object oldValue = vals[index];    vals[index] = maskNull(value);    if (oldValue == null)        size++;    return unmaskNull(oldValue);}

首先調用typeCheck檢查鍵的類型,其代碼為:

private void typeCheck(K key) {    Class keyClass = key.getClass();    if (keyClass != keyType && keyClass.getSuperclass() != keyType)        throw new ClassCastException(keyClass + " != " + keyType);}

如果類型不對,會拋出異常。類型正確的話,調用ordinal擷取索引index,並將值value放入值數組vals[index]中。EnumMap允許值為null,為了區別null值與沒有值,EnumMap將null值封裝成了一個特殊的對象,有兩個輔助方法用於null的打包和解包,打包方法為maskNull,解包方法為unmaskNull。這個特殊對象及兩個方法的代碼為:

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);}

根據鍵擷取值

get方法的代碼為:

public V get(Object key) {    return (isValidKey(key) ?            unmaskNull(vals[((Enum)key).ordinal()]) : null);}

鍵有效話,通過ordinal方法取索引,然後直接在值數組vals裡找。isValidKey的代碼與typeCheck類似,但是返回boolean值而不是拋出異常,代碼為:

private boolean isValidKey(Object key) {    if (key == null)        return false;    // Cheaper than instanceof Enum followed by getDeclaringClass    Class keyClass = key.getClass();    return keyClass == keyType || keyClass.getSuperclass() == keyType;}

查看是否包含某個值

containsValue方法的代碼為:

public boolean containsValue(Object value) {    value = maskNull(value);    for (Object val : vals)        if (value.equals(val))            return true;    return false;}

遍曆值數組進行比較。

根據鍵刪除

remove方法的代碼為:

public V remove(Object key) {    if (!isValidKey(key))        return null;    int index = ((Enum)key).ordinal();    Object oldValue = vals[index];    vals[index] = null;    if (oldValue != null)        size--;    return unmaskNull(oldValue);}

代碼也很簡單,就不解釋了。

實現原理小結

以上就是EnumMap的基本實現原理,內部有兩個數組,長度相同,一個表示所有可能的鍵,一個表示對應的值,值為null表示沒有該索引值對,鍵都有一個對應的索引,根據索引可直接存取和操作其鍵和值,效率很高。

小結

本節介紹了EnumMap的用法和實現原理,用法上,如果需要一個Map且鍵是枚舉類型,則應該用它,簡潔、方便、安全,實現原理上,內部使用數組,根據鍵的枚舉索引直接操作,效率很高。

下一節,我們來看枚舉類型的Set介面的實作類別EnumSet,與之前介紹的Set的實作類別不同,它內部沒有用對應的Map類EnumMap,而是使用了一種極為高效的方式,什麼方式呢?

----------------

未完待續,查看最新文章,敬請關注公眾號“老馬說編程”(掃描下方二維碼),從入門到進階,深入淺出,老馬和你一起探索Java編程及電腦技術的本質。用心原創,保留所有著作權。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.