【Java必修課】HashMap性能很好?問過我EnumMap沒

1 簡介

咱們知道Map只是一個接口,它有多種實現,Java中最經常使用的是HashMap了。而本文想講述的是另外一個實現:EnumMap。它是枚舉類型的Map,要求它的Key值都必須是枚舉型的。java

2 建立你的EnumMap

既然是關於枚舉類型的Map,咱們先建立一個枚舉,以便後續使用:數組

public enum Directions {
    NORTH, SOUTH, EAST, WEST
}

2.1 建立EnumMap的三種方法

JDK提供的建立EnumMap的方法有三種,代碼以下:性能

//new EnumMap
EnumMap<Direction, String> enumMap = new EnumMap<>(Direction.class);
enumMap.put(Direction.EAST, "東");
enumMap.put(Direction.SOUTH, "南");
//從EnumMap複製
EnumMap<Direction, String> enumMapCopyEnumMap = new EnumMap<>(enumMap);
assertEquals(enumMap, enumMapCopyEnumMap);
//從Map複製
Map<Direction, String> hashMap = Maps.newHashMap();
hashMap.put(Direction.EAST, "東");
hashMap.put(Direction.SOUTH, "南");
EnumMap<Direction, String> enumMapCopyHashMap = new EnumMap<>(hashMap);
assertEquals(enumMap, enumMapCopyHashMap);
  • (1) 使用new EnumMap()方法時,與HashMap不一樣,它必須傳入一個枚舉的類型才能建立對象;code

  • (2) 從EnumMap複製,這時傳入的參數爲EnumMap對象

  • (3) 從Map複製,傳入的參數爲Map,但要求Key的類型必須是枚舉型。索引

2.2 聰明的Guava

其實能夠綜合上面三種狀況,實際就是兩種方法:接口

  • (1) 使用new EnumMap(Class<K> keyType)rem

  • (2) 使用new EnumMap(Map<K, ? extends V> m)get

聰明的Guava就只提供了這兩種方法,以下:源碼

//使用Guava建立
EnumMap<Direction, String> enumMapGuava = Maps.newEnumMap(Direction.class);
enumMapGuava.put(Direction.SOUTH, "南");
assertEquals(1, enumMapGuava.size());
enumMapGuava = Maps.newEnumMap(enumMap);
assertEquals(enumMap, enumMapGuava);

3 基本操做

提供的方法與Map固然是同樣的,操做十分方便,代碼以下:

@Test
public void operations() {
  EnumMap<Direction, String> map = Maps.newEnumMap(Direction.class);
  //增長
  map.put(Direction.EAST, "東");
  map.put(Direction.SOUTH, "南");
  map.put(Direction.WEST, "西");
  //查詢
  assertTrue(map.containsKey(Direction.EAST));
  assertFalse(map.containsKey(Direction.NORTH));
  //刪除
  map.remove(Direction.EAST);
  assertFalse(map.containsKey(Direction.EAST));
  assertFalse(map.remove(Direction.WEST, "北"));
  assertTrue(map.remove(Direction.WEST, "西"));
  //清空
  map.clear();
  assertEquals(0, map.size());
}

須要特別指出的是刪除方法,能夠傳入Key和Value兩個參數,map.remove(Direction.WEST, "西")當鍵值對匹配時,則能夠刪除成功;map.remove(Direction.WEST, "北")匹配失敗,則不會刪除。

4 集合視圖

4.1 有序性

與Map接口提供的功能同樣,EnumMap也能返回它的全部Values、Keys和Entry等。但與HashMap不一樣的是,EnumMap返回的視圖是有序的,這個順序不是插入的順序,而是枚舉定義的順序。代碼以下:

EnumMap<Direction, String> map = Maps.newEnumMap(Direction.class);
map.put(Direction.EAST, "東");
map.put(Direction.SOUTH, "南");
map.put(Direction.WEST, "西");
map.put(Direction.NORTH, "北");
//返回全部Value
Collection<String> values = map.values();
values.forEach(System.out::println);
//返回全部Key
Set<Direction> keySet = map.keySet();
keySet.forEach(System.out::println);
//返回全部<Key,Value>
Set<Map.Entry<Direction, String>> entrySet = map.entrySet();
entrySet.forEach(entry -> {
  System.out.println(entry.getKey() + ":" + entry.getValue());
});

輸出的結果以下:

北
南
東
西
NORTH
SOUTH
EAST
WEST
NORTH:北
SOUTH:南
EAST:東
WEST:西

這個順序與咱們定義枚舉的順序確實是同樣的,而與添加的順序無關。

4.2 聯動性

除了有序性以外,EnumMap返回的集合視圖還有一點不一樣就是聯動性,即牽一髮而動全身。改變其中一個,另外的也跟着變了。看代碼一下就明白了:

//Values、keySet、entrySet改變會影響其它
values.remove("東");
assertEquals(3, map.size());
assertEquals(3, keySet.size());
assertEquals(3, entrySet.size());

keySet.remove(Direction.WEST);
assertEquals(2, map.size());
assertEquals(2, values.size());
assertEquals(2, entrySet.size());

entrySet.removeIf(entry -> Objects.equals(entry.getValue(), "北"));
assertEquals(1, map.size());
assertEquals(1, keySet.size());
assertEquals(1, values.size());

//Map的改變會影響其它視圖
map.clear();
assertEquals(0, values.size());
assertEquals(0, keySet.size());
assertEquals(0, entrySet.size());

5 性能

性能是咱們選擇EnumMap的主要緣由之一,那爲什麼它性能會比優秀的HashMap還要好呢?經過看源碼能夠得知:

(1)底層是經過兩個數組來存放數據的,一個放Keys,一個放Values;

(2)由於Key值是枚舉類型,即一開始就肯定了元素個數,因此在建立一個EnumMap的時候,存放數據的數組就已經肯定了大小,不用考慮後續擴容帶來的性能問題。

(3)枚舉自己就是固定順序的,能夠經過Enum.ordinal()方法得到順序,這個即可以做爲查詢與插入的索引,而不用計算HashCode,性能也會比較快。這個順序也就是數組下標。這也是EnumMap的集合視圖都是有序的緣由。

(4)由於大小固定,則不用考慮加載因子,也不會有哈希衝突的問題,空間複雜度小。

6 結論

本文介紹了EnumMap做爲一個Map的特殊實現的建立、使用、集合視圖和性能分析,發現它的確是有過人之處的。當咱們的Key值是枚舉時,不妨能夠試一試EnumMap,性能會更好哦。


歡迎關注公衆號<南瓜慢說>,將持續爲你更新...

多讀書,多分享;多寫做,多整理。

相關文章
相關標籤/搜索