源碼閱讀(24):Java中其它主要的Map結構——LinkedHashMap容器(下)

(接上文《源碼閱讀(23):Java中其它主要的Map結構——LinkedHashMap容器(上)》)

2、LinkedHashMap容器主要操作

2.1、LinkedHashMap容器的實例化方式

LinkedHashMap容器一共有5個構造函數,除了和HashMap容器基本一致的4個構造函數以外,還有一個可以設定accessOrder排序模式的構造函數。下面本文對這幾個構造函數進行描述,注意accessOrder屬性的意義已經在上文中介紹過,這裏就不再贅述了。

// 默認的構造函數
// super()將調用繼承的父類HashMap的對應構造函數
// 使用默認構造函數,將支持insertion-order模式的迭代器遍歷操作
public LinkedHashMap() {
  super();
  accessOrder = false;
}

// 該構造函數將可以設定初始的桶大小,請注意,並不是initialCapacity值和桶大小的關係在本專題介紹HashMap容器時,已經說明
// 願意瞭解的讀者可以參見前文。
// loadFactor爲負載因子
// 使用該構造函數,將支持insertion-order模式的迭代器遍歷操作
public LinkedHashMap(int initialCapacity, float loadFactor) {
  super(initialCapacity, loadFactor);
  accessOrder = false;
}

// 該構造函數將可以通過initialCapacity值設定桶大小
// loadFactor負載因子
// 該構造函數還可以設置LinkedHashMap容器的迭代器遍歷操作模式,爲true時,將採用access-order模式
// 其它情況下將採用insertion-order模式
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
  super(initialCapacity, loadFactor);
  this.accessOrder = accessOrder;
}

// 該構造函數將可以通過initialCapacity值設定桶大小
// 使用該構造函數,將支持insertion-order模式的迭代器遍歷操作
public LinkedHashMap(int initialCapacity) {
  super(initialCapacity);
  accessOrder = false;
}

// 該構造函數將可以傳入其它構造形式的K-V鍵值對集合。
// 並將這些K-V鍵值對對象作爲當前LinkedHashMap容器中的初始K-V鍵值對對象
public LinkedHashMap(Map<? extends K, ? extends V> m) {
  super();
  accessOrder = false;
  putMapEntries(m, false);
}

以上構造函數的過程都很好理解,這裏我們在詳細介紹一下putMapEntries(Map)這個方法。該方法實際上是HashMap容器中的方法,它不止在構造函數中被調用,還在諸如putAll(Map)這樣的方法中被調用:

// 注意,傳入的map容器不能爲null
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  // 參照集合的當前大小賦值給s
  int s = m.size();
  
  if (s > 0) {
    // 當在LinkedHashMap/HashMap容器的構造函數中調用putMapEntries方法時,table就是爲null
    // 這個時候就初始化LinkedHashMap/HashMap容器的容量
    // 注意其中的threshold變量,這是一個全局變量,主要指容器下一次應該擴容的大小
    if (table == null) { 
      float ft = ((float)s / loadFactor) + 1.0F;
      int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);
      // 這個條件當在LinkedHashMap/HashMap容器的構造函數中調用putMapEntries方法時,一定是成立的,
      // 因爲threshold變量的值爲0
      if (t > threshold)
        threshold = tableSizeFor(t);
    }
    // 如果條件成立,說明當前LinkedHashMap/HashMap容器已經存儲了一些K-V鍵值對對象
    // 並且當前需要新增加到容器的參考集合的大小,已經大於了LinkedHashMap/HashMap容器下一次應該擴容的大小
    // 所以在新增前,需要進行LinkedHashMap/HashMap容器的擴容操作
    else if (s > threshold)
      resize();
    
    // 準備好所需要的LinkedHashMap/HashMap容器空間後,
    // 就是用putVal方法,將參考集合中的對象一個一個添加到當期的LinkedHashMap/HashMap容器中
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
      K key = e.getKey();
      V value = e.getValue();
      putVal(hash(key), key, value, false, evict);
    }
  }
}

通過以上的描述,我們知道了LinkedHashMap容器的構造函數,基本上基於HashMap容器的構造函數進行擴展,理解難度不大。

2.2、LinkedHashMap容器的迭代器

LinkedHashMap容器的源代碼中,實際上定義了多種迭代器,這完全是因爲LinkedHashMap容器根據不同的調用要求,可以提供多種不同的迭代方式——可以按照容器中K-V鍵值對對象的key信息進行迭代器遍歷;可以按照容器中K-V鍵值對對象的value信息進行迭代器遍歷;還可以按照容器中K-V鍵值對對象直接進行迭代器遍歷

  • 獲取容器中K-V鍵值對對象的key信息集合,並取得迭代器
// ......
// 該方法用於獲取key信息集合
public Set<K> keySet() {
  // keySet是由AbstractMap類定義的全局變量
  // 用來指向當前已經實例化的key信息集合
  Set<K> ks = keySet;
  // 如果當前keySet沒有值,則生成一個LinkedKeySet的實例
  if (ks == null) {
    ks = new LinkedKeySet();
    keySet = ks;
  }
  return ks;
}

// 以下是LinkedKeySet類的主要定義。
final class LinkedKeySet extends AbstractSet<K> {
  // key信息集合的大小
  public final int size() { return size; }
  // ......  

  // 獲取一個LinkedKeyIterator實例的迭代器
  public final Iterator<K> iterator() {
    return new LinkedKeyIterator();
  }
  // ......
  
  // 通過該方法,可以調用當前的key信息刪除當前容器中對應的K-V鍵值對信息
  // 這個方法實際上是調用的HashMap父類中的removeNode方法
  public final boolean remove(Object key) {
    return removeNode(hash(key), key, null, false, true) != null;
  }
  // ......
  
  // Set.forEach
  public final void forEach(Consumer<? super K> action) {
    if (action == null)
      throw new NullPointerException();
    int mc = modCount;
    for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
      action.accept(e.key);
    if (modCount != mc)
      throw new ConcurrentModificationException();
  }
}

// LinkedHashMap容器中,基於K-V鍵值對對象的key信息集合工作的迭代器,
final class LinkedKeyIterator extends LinkedHashIterator implements Iterator<K> {
  public final K next() { return nextNode().getKey(); }
}

由此我們可以看出LinkedHashMap容器中主要的迭代器實現邏輯是在LinkedHashIterator子類中——這個子類本身並沒有實現Iterator接口,但是它將LinkedHashMap容器中多個種類迭代器的共性放在了一起。以下是該子類的定義:

abstract class LinkedHashIterator {
  // 該變量指向當前迭代結點的下一個結點
  LinkedHashMap.Entry<K,V> next;
  // 該變量指向當前迭代器正在處理的結點
  LinkedHashMap.Entry<K,V> current;
  int expectedModCount;
  
  // 請注意LinkedHashIterator 的構造函數
  LinkedHashIterator() {
    // 全局變量head,來自於當前的的LinkedHashMap容器實例,它代表了LinkedHashMap容器中存在的全局雙向鏈表的開始結點
    next = head;
    // 全局變量modCount,來自於當前的LinkedHashMap容器實例,它代表當前LinkedHashMap容器實例被進行寫操作的次數
    expectedModCount = modCount;
    current = null;
  }
  
  // 如果next變量不爲空,都認爲有下一個迭代要處理的結點
  public final boolean hasNext() {
    return next != null;
  }

  // 該方法獲取迭代器中下一個要處理的結點
  final LinkedHashMap.Entry<K,V> nextNode() {
    // 這個局部變量e,就是要被返回的
    LinkedHashMap.Entry<K,V> e = next;
    // 如果條件成立,說明當前LinkedHashMap容器實例在遍歷過程中,被額外進行了寫操作處理
    // 這個寫操作可能是當前線程執行的,也可能是其它線程執行的
    // 但這是不被允許的(除非寫操作也同時變更了expectedModCount的記錄值),因爲這很可能會更改結點應有的迭代順序
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
    // 如果當前迭代器中沒有了還可以遍歷處理的節點,則也要拋出異常
    if (e == null)
      throw new NoSuchElementException();
    current = e;
    // 將當前結點中after屬性應用的結點作爲下一個要處理的結點,引用給next變量
    next = e.after;
    return e;
  }
  // ......
}

基於這樣的介紹,我們可以得到LinkedHashMap容器中三種迭代器獲取場景的類圖結構:
在這裏插入圖片描述
如上圖所示:無論是根據LinkedHashMap容器中K-V鍵值對對象的key信息獲取迭代器,還是按照容器中K-V鍵值對對象的value信息獲取迭代器,又或者是按照容器中K-V鍵值對對象直接獲取迭代器,它們的獲取方式都可以得到一個特定類型的Set集合(或其它容器),並且這個特定類型的Set集合(或其它容器)都依賴了一種特定工作模式的Iterator迭代器定義方式。而多種特定工作模式的Iterator迭代器,其基本工作原理基本類似,差異只在於其next()方法的實現細節上。接下來我們大致給出這些不同的代碼邏輯差異,如下所示:

  • 獲取容器中K-V鍵值對對象的value信息集合,並取得迭代器
public Collection<V> values() {
  Collection<V> vs = values;
  // 如果條件成比例,則實例化一個LinkedValues類的對象。
  // 和keys()方法中實例化LinkedKeySet類的對象的場景類似
  if (vs == null) {
    vs = new LinkedValues();
    values = vs;
  }
  return vs;
}

// 以下是LinkedValues類的定義,其基本邏輯與LinkedKeySet類中的邏輯類似
// 不同的是它繼承了AbstractCollection類
final class LinkedValues extends AbstractCollection<V> {
  public final int size() { return size; }
  public final void clear() { LinkedHashMap.this.clear(); }
  // LinkedValues類中獲取的迭代器是LinkedValueIterator類的實例。
  public final Iterator<V> iterator() {
    return new LinkedValueIterator();
  }
  // ......
  
  public final void forEach(Consumer<? super V> action) {
    // ......
  }
}

// 以下是LinkedValueIterator類的定義,和LinkedKeyIterator類定義不同的是
// 前者next()方法中返回的是迭代器當前遍歷結點的value值
final class LinkedValueIterator extends LinkedHashIterator implements Iterator<V> {
  public final V next() { return nextNode().value; }
}
  • 按照容器中K-V鍵值對對象獲取迭代器
public Set<Map.Entry<K,V>> entrySet() {
  Set<Map.Entry<K,V>> es;
  return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
  public final int size() { return size; }
  public final void clear() { LinkedHashMap.this.clear(); }
  public final Iterator<Map.Entry<K,V>> iterator() {
    // 這裏創建了LinkedEntryIterator類的實例
    return new LinkedEntryIterator();
  }
  
  // ......
  
  public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
    // ......
  }
}

// 以下是LinkedValueIterator類的定義
final class LinkedEntryIterator extends LinkedHashIterator implements Iterator<Map.Entry<K,V>> {
  public final Map.Entry<K,V> next() { return nextNode(); }
}
發佈了160 篇原創文章 · 獲贊 1730 · 訪問量 154萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章