面試時(shí)被問到Flutter/Dart的HashMap怎么辦线婚?

前言

相信不少同學(xué)在面試的時(shí)候有被問到關(guān)于HashMap的問題狼电,特別是Java/Android程序員蜒灰,HashMap幾乎是必然會(huì)被提及的。因?yàn)檫@里面可以挖掘的點(diǎn)實(shí)在是太多了肩碟。關(guān)于Java的HashMap面經(jīng)在網(wǎng)上可以說(shuō)是隨處可見了强窖。自然而然,隨著Flutter的火爆腾务,后面大家也可能在面試中被問到關(guān)于Flutter/Dart的HashMap相關(guān)問題毕骡。與其到時(shí)候一問三不知,不如現(xiàn)在就來(lái)了解一下Flutter/Dart的HashMap吧岩瘦。

本文中未巫,關(guān)于Dart的HashMap我先列一些有可能在面試中遇到的問題,然后會(huì)對(duì)照源碼做一些介紹启昧,最后會(huì)給出這些問題的一個(gè)粗淺的答案叙凡。希望能幫到大家。

  • HashMapLinkedHashMap有什么區(qū)別密末?
  • 這個(gè)表達(dá)式final map = Map();得到的map是上面兩種Map的那一種握爷?
  • HashMap底層的數(shù)據(jù)結(jié)構(gòu)是什么樣的?
  • HashMap默認(rèn)大小是多大严里?
  • HashMap如何處理hash沖突新啼?
  • HashMap何時(shí)擴(kuò)容?如何擴(kuò)容刹碾?
  • LinkedHashMap底層的數(shù)據(jù)結(jié)構(gòu)是什么樣的燥撞?
  • LinkedHashMap如何處理hash沖突?
  • LinkedHashMap何時(shí)擴(kuò)容迷帜?如何擴(kuò)容物舒?

下面我們就帶著這些問題來(lái)看一下源碼

使用HashMapLinkedHashMap

創(chuàng)建一個(gè)HashMap實(shí)例:

final map = HashMap();

創(chuàng)建一個(gè)LinkedHashMap實(shí)例

final map = LinkedHashMap();

創(chuàng)建一個(gè)Map實(shí)例

final map = Map();

這里你得到的map其實(shí)是一個(gè)LinkedHashMap。其工廠構(gòu)造函數(shù)如下:

@patch
class Map<K, V> {
  ...
  @patch
  factory Map() => new LinkedHashMap<K, V>();
  ...
}

增/改

   map['one'] = 1;

   map.remove('one');

   final value = map['one'];

增戏锹,改冠胯,查的操作和操作數(shù)組一樣,刪除要調(diào)用remove()锦针。

迭代器

   final it = map.entries.iterator;
   while(it.moveNext()) {
      print(it.current);
   }

Dart語(yǔ)言一大特點(diǎn)就是特別靈活荠察,這里只是列舉了一些常見操作置蜀,其他不同的寫法大家可以參考API文檔。

接下來(lái)我們深入源碼來(lái)一探究竟割粮。

HashMap

構(gòu)造函數(shù)

  @patch
  class HashMap<K, V> {
  @patch
  factory HashMap(
      {bool equals(K key1, K key2)?,
      int hashCode(K key)?,
      bool isValidKey(potentialKey)?}) {
        if (isValidKey == null) {
          if (hashCode == null) {
            if (equals == null) {
              return new _HashMap<K, V>();
            }
           ...
  }

HashMap構(gòu)造函數(shù)有三個(gè)可選入?yún)⒍芡耄@里我們都不傳,這樣的話返回的就是個(gè)_HashMap實(shí)例舀瓢。有入?yún)⒌那闆r下會(huì)返回另外兩種_IdentityHashMap_CustomHashMap之一廷雅,本文由于篇幅所限就
不再涉及。大家感興趣的話可以直接去看源碼京髓。

底層結(jié)構(gòu)

var _buckets = List<_HashMapEntry<K, V>?>.filled(_INITIAL_CAPACITY, null);

這一看就是數(shù)組+鏈表的形式嘛航缀。

hashmap.jpg

初始化容量:

static const int _INITIAL_CAPACITY = 8;

我們知道Java的HashMap初始化大小是16,Dart使用的是8. 雖然不同但也還是2的冪堰怨。 另外貌似Dart也沒有給用戶提供自定義初始化大小的機(jī)會(huì)芥玉。

查找操作

  V? operator [](Object? key) {
    final hashCode = key.hashCode;
    final buckets = _buckets;
    final index = hashCode & (buckets.length - 1);
    var entry = buckets[index];
    while (entry != null) {
      if (hashCode == entry.hashCode && entry.key == key) {
        return entry.value;
      }
      entry = entry.next;
    }
    return null;
  }

可見取數(shù)組下標(biāo)就是直接把keyhashCode和數(shù)組長(zhǎng)度-1做與操作。

    final index = hashCode & (buckets.length - 1);

然后比較鏈表元素保存的哈希值以及key是否相等备图,不相等則找下一個(gè)鏈表元素灿巧,都相等則返回對(duì)應(yīng)值。這里我們要注意到?jīng)]有紅黑樹揽涮。所以dart的HashMap實(shí)現(xiàn)其實(shí)和jdk1.8之前的實(shí)現(xiàn)類似抠藕。

賦值操作

void operator []=(K key, V value) {
    final hashCode = key.hashCode;
    final buckets = _buckets;
    final length = buckets.length;
    final index = hashCode & (length - 1);
    var entry = buckets[index];
    while (entry != null) {
      if (hashCode == entry.hashCode && entry.key == key) {
        entry.value = value;
        return;
      }
      entry = entry.next;
    }
    _addEntry(buckets, index, length, key, value, hashCode);
  }

過程和取值操作其實(shí)差不多,鍵值對(duì)存在的情況下就直接賦值蒋困,不存在的情況下就調(diào)用_addEntry()做新增操作盾似。

  void _addEntry(List<_HashMapEntry<K, V>?> buckets, int index, int length,
      K key, V value, int hashCode) {
    final entry = new _HashMapEntry<K, V>(key, value, hashCode, buckets[index]);
    buckets[index] = entry;
    final newElements = _elementCount + 1;
    _elementCount = newElements;
    // If we end up with more than 75% non-empty entries, we
    // resize the backing store.
    if ((newElements << 2) > ((length << 1) + length)) _resize();
    _modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
  }

這里注意一下在新建_HashMapEntry的時(shí)候會(huì)傳入當(dāng)前數(shù)組的entry,也就是buckets[index]雪标。然后把新的entry賦值給buckets[index]零院。

   buckets[index] = entry;

這里我們就能猜到應(yīng)該用的是頭插法。另外村刨,_modificationCount是每次有增刪等操作的時(shí)候都是自增的告抄,當(dāng)我們?cè)诒闅vHashMap的過程中如果有此類操作會(huì)導(dǎo)致Concurrent modification異常。這也就是"Fail-Fast"機(jī)制

新增操作顯然會(huì)涉及到擴(kuò)容的問題嵌牺,從上面的注釋我們可以看出打洼,在鍵值對(duì)數(shù)量超過數(shù)組容量的75%的時(shí)候會(huì)做擴(kuò)容,也就是它的負(fù)載因子是0.75髓梅。這點(diǎn)和Java也是一樣的拟蜻。這個(gè)75%的計(jì)算過程為了提高效率使用了位運(yùn)算和加法來(lái)代替除法绎签,等效于e*4>l*3 -> e/l>3/4 -> e/l>75%

擴(kuò)容操作

void _resize() {
    final oldBuckets = _buckets;
    final oldLength = oldBuckets.length;
    final newLength = oldLength << 1;
    final newBuckets = new List<_HashMapEntry<K, V>?>.filled(newLength, null);
    for (int i = 0; i < oldLength; i++) {
      var entry = oldBuckets[i];
      while (entry != null) {
        final next = entry.next;
        final hashCode = entry.hashCode;
        final index = hashCode & (newLength - 1);
        entry.next = newBuckets[index];
        newBuckets[index] = entry;
        entry = next;
      }
    }
    _buckets = newBuckets;
  }

擴(kuò)容后的新數(shù)組長(zhǎng)度是原長(zhǎng)度的2倍枯饿。

final newLength = oldLength << 1;

我們知道它的初始長(zhǎng)度是8,可見buckets數(shù)組長(zhǎng)度始終會(huì)是2的冪诡必。

從把entry從舊數(shù)組轉(zhuǎn)移到新數(shù)組的過程我們也能看出來(lái)奢方,這個(gè)轉(zhuǎn)移的過程使用的也是頭插法搔扁。

擴(kuò)容這里有一個(gè)需要注意的地方就是,當(dāng)鍵值對(duì)數(shù)量超過數(shù)組長(zhǎng)度的75%時(shí)會(huì)發(fā)生擴(kuò)容蟋字,而不是數(shù)組被占用超過75%的時(shí)候會(huì)發(fā)生擴(kuò)容稿蹲,這一誤區(qū)在很多討論Java HashMap的文章中也出現(xiàn)過。需要大家仔細(xì)體會(huì)這里面的不同鹊奖。

刪除操作

  void _removeEntry(_HashMapEntry<K, V> entry,
      _HashMapEntry<K, V>? previousInBucket, int bucketIndex) {
    if (previousInBucket == null) {
      _buckets[bucketIndex] = entry.next;
    } else {
      previousInBucket.next = entry.next;
    }
  }

刪除就是正常的鏈表刪除節(jié)點(diǎn)的過程苛聘。

遍歷

  void forEach(void action(K key, V value)) {
    final stamp = _modificationCount;
    final buckets = _buckets;
    final length = buckets.length;
    for (int i = 0; i < length; i++) {
      var entry = buckets[i];
      while (entry != null) {
        action(entry.key, entry.value);
        if (stamp != _modificationCount) {
          throw new ConcurrentModificationError(this);
        }
        entry = entry.next;
      }
    }
  }

遍歷會(huì)從數(shù)組的第一個(gè)位置開始依次訪問鏈表的每一項(xiàng)。顯然這個(gè)遍歷順序是不保證和鍵值對(duì)的插入順序一致的忠聚。這里我們也能看到"Fail-Fast"機(jī)制發(fā)生作用的時(shí)候了设哗,如果遍歷過程中用戶對(duì)HashMap做了增刪等操作的話會(huì)導(dǎo)致stamp_modificationCount不相等,導(dǎo)致ConcurrentModificationError異常两蟀。

小結(jié)

Dart的HashMap總體而言實(shí)現(xiàn)的還是比較簡(jiǎn)單的网梢。整體上和jdk1.7版本的HashMap類似。相信研究過Java實(shí)現(xiàn)的同學(xué)們會(huì)覺得dart的HashMap比較好理解一些赂毯。

LinkedHashMap

從API文檔上看战虏,LinkedHashMapHashMap的區(qū)別就是在遍歷的時(shí)候,LinkedHashMap會(huì)保留鍵值對(duì)的插入順序党涕。在jdk中烦感,LinkedHashMap的實(shí)現(xiàn)是將Node改造為雙向鏈表以保留順序。dart的LinkedHashMap實(shí)現(xiàn)則有所不同遣鼓。

構(gòu)造函數(shù)

@patch
class LinkedHashMap<K, V> {
  @patch
  factory LinkedHashMap(
      {bool equals(K key1, K key2)?,
      int hashCode(K key)?,
      bool isValidKey(potentialKey)?}) {
    if (isValidKey == null) {
      if (hashCode == null) {
        if (equals == null) {
          return new _InternalLinkedHashMap<K, V>();
        }
       ...
  }

類似的啸盏,LinkedHashMap構(gòu)造函數(shù)有三個(gè)可選入?yún)ⅲ@里我們都不傳骑祟,這樣的話返回的就是個(gè)_InternalLinkedHashMap實(shí)例回懦。有入?yún)⒌那闆r下會(huì)返回另外兩種_CompactLinkedIdentityHashMap_CompactLinkedCustomHashMap之一,本文也不再涉及次企。

底層結(jié)構(gòu)

我們直接看_InternalLinkedHashMap怯晕。

_InternalLinkedHashMap構(gòu)造函數(shù):

  _InternalLinkedHashMap() {
    _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE);
    _hashMask = _HashBase._indexSizeToHashMask(_HashBase._INITIAL_INDEX_SIZE);
    _data = new List.filled(_HashBase._INITIAL_INDEX_SIZE, null);
    _usedData = 0;
    _deletedKeys = 0;
  }

可見_InternalLinkedHashMap底層有兩個(gè)數(shù)組,_index_data缸棵。_index數(shù)組以哈希碼為下標(biāo)記錄對(duì)應(yīng)鍵值對(duì)在_data數(shù)組中的位置舟茶。_data數(shù)組按插入順序依次保存keyvalue

用圖來(lái)表示就是下面這個(gè)樣子:

Untitled Diagram-2.png

兩個(gè)數(shù)組的初始化長(zhǎng)度都是_INITIAL_INDEX_SIZE堵第。通過以下代碼可見其值為16吧凉。_data數(shù)組存放的是鍵值對(duì),那最多的話只能存放8個(gè)鍵值對(duì)了踏志。也就是說(shuō)LinkedHashMap初始容量其實(shí)是8阀捅。

 abstract class _HashBase implements _HashVMBase {
 ...
  static const int _INITIAL_INDEX_BITS = 3;
  static const int _INITIAL_INDEX_SIZE = 1 << (_INITIAL_INDEX_BITS + 1);
  }

LinkedHashMap底層其實(shí)是用線性探測(cè)法實(shí)現(xiàn)的。數(shù)組_index里保存的是叫pair的一個(gè)整數(shù)针余。之所以叫pair是因?yàn)樗怯筛呶缓偷臀粌刹糠纸M成的饲鄙,高位叫hashPattern, 低位叫entry凄诞。entry指向_data數(shù)組對(duì)應(yīng)的鍵值對(duì)。從hashcode到真正鍵值對(duì)的查找過程的關(guān)鍵點(diǎn)其實(shí)就是這個(gè)entry忍级。

同時(shí)pair也用來(lái)表示_index數(shù)組對(duì)應(yīng)位置的狀態(tài)帆谍。0(_UNUSED_PAIR)表示當(dāng)前未使用,1(_DELETED_PAIR)表示當(dāng)前位置處于刪除狀態(tài):

abstract class _HashBase implements _HashVMBase {
    ...
  static const int _UNUSED_PAIR = 0;
  static const int _DELETED_PAIR = 1;
   ...
}
Screen Shot 2021-05-21 at 12.16.13 AM.png

查找操作

  V? operator [](Object? key) {
    var v = _getValueOrData(key);
    return identical(_data, v) ? null : internal.unsafeCast<V>(v);
  }

查找最終會(huì)調(diào)用到_getValueOrData

Object? _getValueOrData(Object? key) {
    final int size = _index.length;
    final int sizeMask = size - 1;
    final int maxEntries = size >> 1;
    final int fullHash = _hashCode(key);
    final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size);
    int i = _HashBase._firstProbe(fullHash, sizeMask);
    int pair = _index[i];
    while (pair != _HashBase._UNUSED_PAIR) {
      if (pair != _HashBase._DELETED_PAIR) {
        final int entry = hashPattern ^ pair;
        if (entry < maxEntries) {
          final int d = entry << 1;
          if (_equals(key, _data[d])) {
            return _data[d + 1];
          }
        }
      }
      i = _HashBase._nextProbe(i, sizeMask);
      pair = _index[i];
    }
    return _data;
  }

從這個(gè)函數(shù)中我們就能了解到線性探測(cè)的過程了轴咱。首先通過調(diào)用_HashBase._firstProbe()來(lái)拿到首個(gè)地址:

static int _firstProbe(int fullHash, int sizeMask) {
    final int i = fullHash & sizeMask;
    // Light, fast shuffle to mitigate bad hashCode (e.g., sequential).
    return ((i << 1) + i) & sizeMask;
  }

首次探測(cè)就是把hashcode和數(shù)組長(zhǎng)度取模汛蝙,注意還有一步,是把i乘以3以后再取模朴肺。從注釋上看是為了使hashcode分布更均勻一些患雇。大家可以思考一下其中的原因。

首次探測(cè)以后拿到pair宇挫,如果這個(gè)pair是未占用狀態(tài)說(shuō)明鍵值對(duì)不存在苛吱,按約定直接返回_data數(shù)組。如果是刪除狀態(tài)就接著做二次探測(cè)器瘪。如果是正常占用狀態(tài)翠储,就將pairhashPattern做異或,從前面的圖可知橡疼,這樣就得到了entry援所。檢查entry未越界的話,將其乘以2就是key_data數(shù)組中的位置了欣除,最后判斷key相等住拭,則返回_data的下一個(gè)元素,即value历帚。

二次探測(cè)會(huì)調(diào)用_HashBase._nextProbe()

static int _nextProbe(int i, int sizeMask) => (i + 1) & sizeMask;

源碼可見就是挨個(gè)去試下一個(gè)地址滔岳。

賦值操作

  void operator []=(K key, V value) {
    final int size = _index.length;
    final int fullHash = _hashCode(key);
    final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size);
    final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size);
    if (d > 0) {
      _data[d] = value;
    } else {
      final int i = -d;
      _insert(key, value, hashPattern, i);
    }
  }

賦值時(shí)會(huì)先調(diào)用_findValueOrInsertPoint()來(lái)尋找已存在的鍵值對(duì),其邏輯和函數(shù)_getValueOrData類似挽牢。鍵值對(duì)存在則直接返回對(duì)應(yīng)value_data中的位置谱煤,然后直接賦值就完了。如果不存在的話禽拔,就會(huì)返回一個(gè)負(fù)數(shù)刘离,這個(gè)負(fù)數(shù)其實(shí)就是經(jīng)過探測(cè)以后在_index數(shù)組中的可用位置。有了這個(gè)位置就可以調(diào)用_insert()來(lái)做插入操作了睹栖。

void _insert(K key, V value, int hashPattern, int i) {
    if (_usedData == _data.length) {
      _rehash();
      this[key] = value;
    } else {
      assert(1 <= hashPattern && hashPattern < (1 << 32));
      final int index = _usedData >> 1;
      assert((index & hashPattern) == 0);
      _index[i] = hashPattern | index;
      _data[_usedData++] = key;
      _data[_usedData++] = value;
    }
  }

插入之前先判斷_data數(shù)組是否已占滿硫惕。滿了的話就要調(diào)用_rehash()做擴(kuò)容了。未滿的話就是簡(jiǎn)單的賦值操作了野来,將_data的下一個(gè)空位除以2以后和hashPattern做或運(yùn)算恼除,然后放入_index數(shù)組,再將keyvalue緊挨著放入_data數(shù)組梁只。

擴(kuò)容操作

void _rehash() {
    if ((_deletedKeys << 2) > _usedData) {
      _init(_index.length, _hashMask, _data, _usedData);
    } else {
      _init(_index.length << 1, _hashMask >> 1, _data, _usedData);
    }
  }

擴(kuò)容之前先看數(shù)組中是否有超過一半的元素是處于刪除狀態(tài)缚柳,是的話擴(kuò)容長(zhǎng)度和原數(shù)組長(zhǎng)度是一樣的,否則新數(shù)組長(zhǎng)度為原長(zhǎng)的2倍搪锣。為什么這么操作秋忙,我們接著看_ininit()函數(shù)。

void _init(int size, int hashMask, List? oldData, int oldUsed) {
    _index = new Uint32List(size);
    _hashMask = hashMask;
    _data = new List.filled(size, null);
    _usedData = 0;
    _deletedKeys = 0;
    if (oldData != null) {
      for (int i = 0; i < oldUsed; i += 2) {
        var key = oldData[i];
        if (!_HashBase._isDeleted(oldData, key)) {
          this[key] = oldData[i + 1];
        }
      }
    }
  }

這里會(huì)按新的長(zhǎng)度新建_index_data數(shù)組构舟。接著會(huì)做拷貝灰追,如果是已刪除狀態(tài)的鍵值對(duì)是不會(huì)被拷貝的。所以和原數(shù)組一樣長(zhǎng)的"擴(kuò)容"過程其實(shí)就是把被刪除的元素真正刪除了狗超。

刪除操作

V? remove(Object? key) {
    final int size = _index.length;
    final int sizeMask = size - 1;
    final int maxEntries = size >> 1;
    final int fullHash = _hashCode(key);
    final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size);
    int i = _HashBase._firstProbe(fullHash, sizeMask);
    int pair = _index[i];
    while (pair != _HashBase._UNUSED_PAIR) {
      if (pair != _HashBase._DELETED_PAIR) {
        final int entry = hashPattern ^ pair;
        if (entry < maxEntries) {
          final int d = entry << 1;
          if (_equals(key, _data[d])) {
            _index[i] = _HashBase._DELETED_PAIR;
            _HashBase._setDeletedAt(_data, d);
            V value = _data[d + 1];
            _HashBase._setDeletedAt(_data, d + 1);
            ++_deletedKeys;
            return value;
          }
        }
      }
      i = _HashBase._nextProbe(i, sizeMask);
      pair = _index[i];
    }
    return null;
  }

刪除過程首先也是做線性探測(cè)弹澎。找到了的話就做兩件事,首先將_index數(shù)組對(duì)應(yīng)位置置為_HashBase._DELETED_PAIR努咐。然后將_data數(shù)組對(duì)應(yīng)位置置為_data苦蒿。

遍歷

我們知道,LinkedHashMap則會(huì)保證遍歷順序和插入順序相同渗稍。那通過上面介紹我們了解到插入的鍵值對(duì)都按順序保存在_data數(shù)組中了佩迟。那么遍歷的時(shí)候只需要遍歷_data數(shù)組自然就能按插入順序遍歷LinkedHashMap了。

bool moveNext() {
    if (_table._isModifiedSince(_data, _checkSum)) {
      throw new ConcurrentModificationError(_table);
    }
    do {
      _offset += _step;
    } while (_offset < _len && _HashBase._isDeleted(_data, _data[_offset]));
    if (_offset < _len) {
      _current = internal.unsafeCast<E>(_data[_offset]);
      return true;
    } else {
      _current = null;
      return false;
    }
  }

如果是刪除狀態(tài)的鍵值對(duì)是會(huì)被跳過的竿屹。

小結(jié)

Dart的LinkedHashMap實(shí)現(xiàn)和jdk的是不同的报强,大家初次接觸的話可能會(huì)比較陌生,需要仔細(xì)研究一下源碼來(lái)看看具體實(shí)現(xiàn)拱燃,也是能學(xué)到一些東西的秉溉。

總結(jié)

總體來(lái)說(shuō)Dart的HashMapLinkedHashMap實(shí)現(xiàn)還是比較簡(jiǎn)單的,并沒有像jdk一樣做一些細(xì)致的優(yōu)化工作碗誉,這可能有待于Dart/Flutter的進(jìn)一步發(fā)展吧召嘶。但我們也能看到不論是何種語(yǔ)言,一些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)其設(shè)計(jì)思想都是相通的哮缺。掌握源碼背后的設(shè)計(jì)思路就可以舉一反三苍蔬,不論是哪種新語(yǔ)言,新架構(gòu)都可以快速掌握蝴蜓,最后碟绑,我把最開始的那幾個(gè)問題連帶粗淺的答案一起放在下面:

HashMapLinkedHashMap有什么區(qū)別?

從API角度茎匠,HashMap遍歷的時(shí)候不保證和插入順序相同格仲,而LinkedHashMap則會(huì)保證遍歷順序和插入順序相同。

這個(gè)表達(dá)式final map = Map();得到的map是上面兩種Map的那一種诵冒?

LinkedHashMap凯肋。

HashMap底層的數(shù)據(jù)結(jié)構(gòu)是什么樣的?

數(shù)組+鏈表汽馋。

HashMap默認(rèn)大小是多大侮东?

8圈盔。

HashMap如何處理hash沖突?

鏈地址法悄雅。

HashMap何時(shí)擴(kuò)容驱敲?如何擴(kuò)容?

當(dāng)鍵值對(duì)數(shù)量超過數(shù)組長(zhǎng)度的75%時(shí)會(huì)發(fā)生擴(kuò)容宽闲,而不是數(shù)組被占用超過75%的時(shí)候會(huì)發(fā)生擴(kuò)容众眨。擴(kuò)容后的新數(shù)組長(zhǎng)度將會(huì)是原數(shù)組長(zhǎng)度的2倍,擴(kuò)容過程采用頭插法容诬。

LinkedHashMap底層的數(shù)據(jù)結(jié)構(gòu)是什么樣的娩梨?

兩個(gè)數(shù)組:_index_data, _index數(shù)組以哈希碼為下標(biāo)記錄對(duì)應(yīng)鍵值對(duì)在_data數(shù)組中的位置。_data數(shù)組按插入順序依次保存keyvalue览徒。

LinkedHashMap如何處理hash沖突狈定?

線性探測(cè)法。

LinkedHashMap何時(shí)擴(kuò)容习蓬?如何擴(kuò)容掸冤?

_data數(shù)組滿時(shí)擴(kuò)容,擴(kuò)容之前先看數(shù)組中是否有超過一半的元素是處于刪除狀態(tài)友雳,是的話擴(kuò)容長(zhǎng)度和原數(shù)組長(zhǎng)度是一樣的稿湿,否則新數(shù)組長(zhǎng)度為原長(zhǎng)的2倍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末押赊,一起剝皮案震驚了整個(gè)濱河市饺藤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌流礁,老刑警劉巖涕俗,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異神帅,居然都是意外死亡再姑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門找御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)元镀,“玉大人,你說(shuō)我怎么就攤上這事霎桅∑芤桑” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵滔驶,是天一觀的道長(zhǎng)遇革。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么萝快? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任锻霎,我火速辦了婚禮,結(jié)果婚禮上揪漩,老公的妹妹穿的比我還像新娘旋恼。我一直安慰自己,他們只是感情好氢拥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锨侯,像睡著了一般嫩海。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上囚痴,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天叁怪,我揣著相機(jī)與錄音,去河邊找鬼深滚。 笑死奕谭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痴荐。 我是一名探鬼主播血柳,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼生兆!你這毒婦竟也來(lái)了难捌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸦难,失蹤者是張志新(化名)和其女友劉穎根吁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體合蔽,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡击敌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拴事。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沃斤。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刃宵,靈堂內(nèi)的尸體忽然破棺而出轰枝,到底是詐尸還是另有隱情,我是刑警寧澤组去,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布鞍陨,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诚撵。R本人自食惡果不足惜缭裆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寿烟。 院中可真熱鬧澈驼,春花似錦、人聲如沸筛武。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徘六。三九已至内边,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間待锈,已是汗流浹背漠其。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竿音,地道東北人和屎。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像春瞬,于是被迫代替她去往敵國(guó)和親柴信。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容