源碼閱讀(16):Java中主要的Map結構——HashMap容器(上)

(接上文《源碼閱讀(15):Java中主要的Map結構——概述》)

2.4、java.util.AbstractMap抽象類

AbstractMap抽象類是實現了Map接口的一個抽象類,用來向下層具體的Map容器實現提供了一些默認的功能實現邏輯,以便減少下層具體的Map容器構建過程的代碼量,降低自定義Map容器的實現難度。

2.4.1、AbstractMap抽象類基本介紹

AbstractMap抽象類中已實現的那些默認邏輯,實際上不能獨立運行,因爲這些實現過程中的重要過程都是缺失的。例如AbstractMap抽象類中對size()方法進行了實現,如下所示:

public abstract class AbstractMap<K,V> implements Map<K,V> {
  // ......
  public int size() {
    return entrySet().size();
  } 
  // ......
}

仔細觀察就會發現,以上代碼片段使用了Map接口中定義的entrySet()方法,後者將返回Map容器中存儲的Map.Entry對象集合。並且通過entrySet()方法返回的Map.Entry對象集合還有一個特點,就是其中的對象信息不回重複。Map.Entry接口已經在上文介紹過了,每一個Map.Entry對象,就表示存儲在Map容器中的每一個K-V鍵值對信息。但可惜的是AbstractMap抽象類中並沒有對entrySet()方法進行實現,而是要求下層具體的Map容器進行實現:

public abstract class AbstractMap<K,V> implements Map<K,V> {
  // ......
  public abstract Set<Entry<K,V>> entrySet();
  // ......
}

例如java.util.HashMap類中對以上方法的實現如下所示:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
  // ......
  public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
  }
  // ......
}

就像上文介紹的那樣,AbstractMap抽象類的存在目的是爲了減少程序員實現具體Map容器時的編碼工作量。AbstractMap抽象類還爲下層不同性質的Map容器實現提供了編碼建議。例如,如果基於AbstractMap抽象類實現的具體Map容器是一個不可新增K-V鍵值對信息的容器,那麼程序員只需要實現上文中提到的entrySet()方法即可,因爲AbstractMap抽象類中定義了不支持put()方法和remove()方法,如下所示:

public abstract class AbstractMap<K,V> implements Map<K,V> {
  // ......
  public V put(K key, V value) {
    throw new UnsupportedOperationException();
  }
  // ......
}

// AbstractMap類中實現的remove()方法,最終將依賴使用Iterator迭代器中的remove()方法。
public interface Iterator<E> {
  default void remove() {
    throw new UnsupportedOperationException("remove");
  }
}

再例如如果基於AbstractMap抽象類實現的具體Map容器是一個可新增K-V鍵值對信息的容器,則需要這個具體的Map容器自行實現put()方法,以及entrySet()方法所使用的迭代器中的remove()方法——具體來說就是以上代碼片段中提到的java.util.Iterator接口中的remove()方法。

To implement an unmodifiable map, the programmer needs only to extend thisclass and provide an implementation for the entrySet method, whichreturns a set-view of the map’s mappings. Typically, the returned setwill, in turn, be implemented atop AbstractSet. This set shouldnot support the add or remove methods, and its iteratorshould not support the remove method.
 
To implement a modifiable map, the programmer must additionally overridethis class’s put method (which otherwise throws an UnsupportedOperationException), and the iterator returned by entrySet().iterator() must additionally implement its remove method.

2.4.2、AbstractMap抽象類中的SimpleEntry

AbstractMap抽象類還提供了一個Map.Entry接口的默認實現實現——AbstractMap.SimpleEntry。後者對一個K-V鍵值對進行了簡單的定義:

public static class SimpleEntry<K,V>
        implements Entry<K,V>, java.io.Serializable
{
  private final K key;
  private V value;
  public SimpleEntry(K key, V value) {
    this.key   = key;
    this.value = value;
  }

  // ......
  /**
   * Returns the key corresponding to this entry.
   * @return the key corresponding to this entry
   */
  public K getKey() {
      return key;
  }

  /**
   * Returns the value corresponding to this entry.
   * @return the value corresponding to this entry
   */
  public V getValue() {
      return value;
  }
  
  // ......
}

這個實現很簡單,以至於直接使用該類的具體容器並不多,舉例如下:
在這裏插入圖片描述
比如本專題後文將詳細介紹的HashMap容器中,就自行實現了Map.Entry接口,如下所示:

/**
 * Basic hash bin node, used for most entries.  (See below for
 * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
 */
static class Node<K,V> implements Map.Entry<K,V> {
  // ......
  final int hash;
  final K key;
  V value;
  Node<K,V> next;

  Node(int hash, K key, V value, Node<K,V> next) {
    this.hash = hash;
    this.key = key;
    this.value = value;
    this.next = next;
  }
  // ......
}

3、主要的Map結構——HashMap容器

HashMap容器從字面的理解就是,基於Hash算法構造的Map容器。從數據結構的知識體系來說,HashMap容器是散列表在Java中的具體表達(並非線性表結構)。具體來說就是,利用K-V鍵值對中鍵對象的某個屬性(默認使用該對象的“內存起始位置”這一屬性)作爲計算依據進行哈希計算(調用hashCode方法),然後再以計算後的返回值爲依據,將當前K-V鍵值對在符合HashMap容器構造原則的基礎上,放置到HashMap容器的某個位置上,且這個位置和之前添加的K-V鍵值對的存儲位置完全獨立,不一定構成連續的存儲關係。

hashCode()方法遵循以下默認實現原則:
1、默認的要求下:在同一進程中的同一對象,無論它的hashCode()方法被調用多少次,返回的值都是一樣的。
 
2、默認要求下,如果根據對象的equals(object)方法進行比較,得到兩個對象相等的結果。那麼調用兩個對象的hashCode()方法就會得到相同的返回值;換句話說,這種情況下,如果調用兩個對象的hashCode()方法得到了不同的返回值,那麼根據對象的equals(object)方法進行比較,將得到兩個對象不相等的結果。

請記住以上規則,在後續講解HashMap容器的核心工作原理時,會默認爲讀者已經知曉以上知識點。這裏舉例說明Object這個類中的equals(object)方法和hashCode()方法:

public class Object {
  // Object類中的hashCode方法使用JNI進行實現
  // 實際上就是基於對象的內存起始地址得到一個hash值
  public native int hashCode();
  
  // Object類中的equals()方法,使用兩個對象的內存地址起始位置得到對比結果
  // 這兩個方法的實現原則結果很明顯遵循了上文中提到的原則
  public boolean equals(Object obj) {
    return (this == obj);
  }
}

3.1、HashMap基本使用

HashMap容器的使用方法很簡單,如下所示:

// ......
// 以下代碼示例了最簡單的HashMap使用方式
HashMap<String, String> map = new HashMap<>();
// Key建信息採用字符串作爲標識
// Value值信息也是字符串類型
map.put("key1", "vlaue1");
map.put("key2", "vlaue2");
map.put("key3", "vlaue3");
map.put("key4", "vlaue4");
map.put("key5", "vlaue4");
/*
 * 值信息允許重複,但鍵信息不允許重複:
 * key3的鍵信息在這裏重新關聯的值是valueX
 * 那麼之前的value3的值信息將被替換
 * */
map.put("key3", "vlaueX");

// 通過以下迭代器的輸出,我們可以知道
// 若干鍵信息在HashMap中並不是順序存儲的
Set<String> keys = map.keySet();
for (String key : keys) {
  System.out.println(String.format("key = %s and key's value = %s"  , key , map.get(key)));
}

以上代碼的執行後輸出的效果爲:

key = key1 and key's value = vlaue1
key = key2 and key's value = vlaue2
key = key5 and key's value = vlaue4
key = key3 and key's value = vlaueX
key = key4 and key's value = vlaue4

除了java.lang.String類型的Value信息以外,HashMap可以存儲任意類型的Value信息,只不過如果要對HashMap進行序列化/反序列化,就需要這個Value信息的類實現了java.io.Serializable標識接口,關於HashMap的序列化和反序列化過程在本文後續有專門講解。以下代碼示意了的Value信息類型是由程序員自定義的MyObject類:

// 當然value信息的類型還可以是其它類,例如我們自定義的MyObject類
HashMap<String, MyObject> myMap = new HashMap<>();
myMap.put("key2", new MyObject("field21", "filed22"));
myMap.put("key3", new MyObject("field31", "filed32"));
myMap.put("key4", new MyObject("field41", "filed42"));
myMap.put("key5", new MyObject("field51", "filed52"));
myMap.put("key6", new MyObject("field61", "filed62"));
// 同樣通過以下迭代也可以觀察到key並不是順序存儲的
Set<String> keys = myMap.keySet();
for (String key : keys) {
  System.out.println(String.format("key = %s and key's value = {%s}"  , key , myMap.get(key)));
}

// 這是程序員自定義的MyObject類
static class MyObject {
  private String filed1;
  private String filed2;
  // ...... 還有很多屬性(get/set就不再贅述了)
  public String toString() {
    return String.format("field1 = %s , field2 = %s", this.filed1 , this.filed2);
  }
}

當然HashMap中的key信息也可以是除了除了java.lang.String類以外的任何類定義,但是Key的類型在整個HashMap容器工作機制中就比較微妙了,本專題將通過後文的詳細介紹,力圖把這個問題講清楚。

================
(接下文《源碼閱讀(17):Java中主要的Map結構——HashMap容器(中)》)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章