Java入門記(五):容器關係的梳理(下)——Map

注意:閱讀本文及相關源碼時,須要數據結構相關知識,包括:哈希表、鏈表、紅黑樹。html

 

  Map是將鍵(key)映射到值(value)的對象。不一樣的映射不能包含相同的鍵;每一個鍵最多隻能映射到一個值。下圖是常見Map的接口和實現。與Collection相比,繼承關係簡單很多。算法

1、Map接口和AbstractMap抽象類

  Map接口除了增長映射、根據key獲取value、判斷映射中的key或value是否存在、刪除映射的基本方法外,還包含了返回包含全部key的Set、包含全部value的collection的方法。因爲key不能重複,返回的Collection天然具備Set的屬性,很適合用Set返回。而value則不行。
數組

  與其餘Collection接口不一樣,Map接口中有一個子接口:Entry。Entry表明了一個映射,包含了key和value兩部分,同時,一個Enry的key沒有提供修改方法,而value容許修改。須要說明的是,若是用一個可變對象做爲Map的key,若變化後equals()與以前的行爲不一樣,那麼映射的行爲是不肯定的(JDK1.6文檔)。安全

  對於抽象類AbstractMap,大部分實現的方法藉助了將全部entry組成的set返回的抽象方法entrySet():size()、isEmpty()(使用size())、containsValue()、containsKey()、get()、clear()、keySet()、values()等。而remove()、removeAll()、retainAll()、clear()、toString()則藉助了抽象方法iterator()。數據結構

  values()返回值value的是一個匿名內部類實現的AbstractCollection。value在第一次訪問時建立,在後續全部訪問中返回。雖然不進行元素的同步,其引用幾乎老是不變的,但返回值的行爲會隨着Map中的元素變化:app

 

public Collection<V> values() {
    if (values == null) {
        values = new AbstractCollection<V>() {
          public Iterator<V> iterator() {
             return new Iterator<V>() {
              private Iterator<Entry<K,V>> i = entrySet().iterator();

              public boolean hasNext() {
                  return i.hasNext();
              }

              public V next() {
                  return i.next().getValue();
              }

              public void remove() {
                  i.remove();
              }
            };
            }

          public int size() {
             return AbstractMap.this.size();
          }

          public boolean contains(Object v) {
              return AbstractMap.this.containsValue(v);
           }
        };
    }
    return values;
}

 

  對於Map.Entry,AbstractMap中實現了兩個鍵值對類型:SimpleEntry和SimpleImmutableEntry。後者與前者的區別是,不容許setValue(),調用該方法拋出UnsupportedOperationException異常。post

2、HashMap/LinkedHashMap/WeakHashMap

   在Java入門記(四):容器關係的梳理(上)——Collection一文中提到,HashSet/LinkedHashSet的底層實際是HashMap/LinkedHashMap。HashMap和通常的散列表實現方式相同,用數組存放相同哈希值的元素所組成隊列的首元素,隊列的元素是Entry,包括了key、value、hash值、next等屬性。尋找指定key時,先作哈希,根據哈希值找到數組中對應的隊列頭,遍歷隊列找出key及對應的value。this

  因爲HashMap容許null做爲key,這個key沒辦法作哈希值的計算,只能遍歷哈希值數組,找到首元素的key爲null的隊列。這個實現能夠參考私有方法getForNullKey()。url

  HashMap的key的哈希值數組有一個容量限制:必須爲2的冪次。即便在新建HashMap時或調用resize()時指定一個非2的冪次的容量,實際調用時新的HashMap容量也會擴大到不小於這個指定容量的2的冪次的值。與之相關聯地,哈希值的計算hash()和某個hash值在數組的索引的計算indexFor()方式爲:spa

static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    return h & (length-1);
}

2的冪次能夠保證計算索引時適當的截斷(捨棄高位)。

   LinkedHashMap與HashMap的關係並不像LinkedList和ArrayList那樣。從結構上來看,LinkedHashMap僅僅是把全部的Entry組成了一個雙向鏈表。這樣,在迭代遍歷時,可使用插入順序或LRU順序訪問全部元素(經過設置accessOrder標記位)。

  WeakHashMap和HashMap很相似,其內部包含了一個ReferenceQueue,而且它的Entry是繼承自WeakReference的。經過這種方式,在clear()、resize()、size()、getTable()時,都會調用expungeStaleEntries()方法,垃圾回收掉再也不使用的映射關係。這裏不介紹Reference的相關內容了。

  思考下上一篇文章所提出的問題:是否是能夠先實現HashSet,再用HashSet實現HashMap?我的認爲,這樣實現的HashSet中的元素(對應Map的Entry),只有鍵沒有值,是沒法直接實現HashMap的。

2、Hashtable/Properties

  Hashtable雖然實現了Map接口,但沒有用AbstractMap來作。它的行爲與HashMap很類似,保留下來是爲了兼容原來的代碼,不推薦繼續使用。

  繼承了Hashtable<Object,Object>的Properties稍有點不一樣,它與流的關係密切些,可保存在流中或從流中加載。另外,它是線程安全的。

3、SortedMap和TreeMap

  SortedMap中的全部元素都是排過序的。這個「排序」不一樣於LinkedHashMap中將全部元素組織成一個鏈表,而是指任意任意兩個元素均可以比較大小關係,並根據這個比較規則Comparator進行排序。更準確的說,是鍵的大小關係。創建在有序的基礎上,SortedMap接口中包含了返回部分Map的方法subMap(K fromKey, K toKey)、headMap(K toKey)、tailMap(K fromKey)以及首尾key的方法firstKey()、lastKey()。

  TreeMap是SortedMap的一個實現,其Compartor能夠爲null,這種狀況下比較元素大小時調用元素自身的compareTo()方法。

  TreeMap實際上使用了紅黑樹,保存了樹的根。關於紅黑樹的算法,講起來能夠單獨開一篇文章,這裏不展開了,想了解的讀者能夠讀下《算法導論》的相關章節。TreeMap的元素插入、刪除實際上是紅黑樹節點的插入和刪除。在元素有序的前提下,找到特定的key(以及對應的value)一樣是使用了紅黑樹的查找方法。

相關文章
相關標籤/搜索