<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          [文末贈(zèng)書]面試被問到Flutter/Dart的HashMap你會(huì)嗎?

          共 26958字,需瀏覽 54分鐘

           ·

          2021-06-06 10:28

          點(diǎn)擊上方 Java學(xué)習(xí)之道,選擇 設(shè)為星標(biāo)

          每天18:30點(diǎn),干貨準(zhǔn)時(shí)奉上!

          來源: juejin.cn/post/6964427316046856229
          作者: ad6623

          Part1前言

          相信不少同學(xué)在面試的時(shí)候有被問到關(guān)于HashMap的問題,特別是Java/Android程序員,HashMap幾乎是必然會(huì)被提及的。因?yàn)檫@里面可以挖掘的點(diǎn)實(shí)在是太多了。關(guān)于Java的HashMap面經(jīng)在網(wǎng)上可以說是隨處可見了。自然而然,隨著Flutter的火爆,后面大家也可能在面試中被問到關(guān)于Flutter/Dart的HashMap相關(guān)問題。與其到時(shí)候一問三不知,不如現(xiàn)在就來了解一下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ò)容?

          下面我們就帶著這些問題來看一下源碼

          Part2使用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<KV{
            ...
            @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語言一大特點(diǎn)就是特別靈活,這里只是列舉了一些常見操作,其他不同的寫法大家可以參考API文檔。

          接下來我們深入源碼來一探究竟。

          Part3HashMap

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

          @patch
            class HashMap<KV{
            @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ù)組+鏈表的形式嘛。

          初始化容量:

          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)算和加法來代替除法,等效于e4>l3 -> 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ù)組的過程我們也能看出來,這個(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比較好理解一些。

          Part4LinkedHashMap

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

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

          @patch
          class LinkedHashMap<KV{
            @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

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

          Untitled Diagram-2.png

          兩個(gè)數(shù)組的初始化長(zhǎng)度都是_INITIAL_INDEX_SIZE。通過以下代碼可見其值為16。_data數(shù)組存放的是鍵值對(duì),那最多的話只能存放8個(gè)鍵值對(duì)了。也就是說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也用來表示_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()來拿到首個(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)說明鍵值對(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()來尋找已存在的鍵值對(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()來做插入操作了。

          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ì)研究一下源碼來看看具體實(shí)現(xiàn),也是能學(xué)到一些東西的。

          Part5總結(jié)

          總體來說Dart的HashMapLinkedHashMap實(shí)現(xiàn)還是比較簡(jiǎn)單的,并沒有像jdk一樣做一些細(xì)致的優(yōu)化工作,這可能有待于Dart/Flutter的進(jìn)一步發(fā)展吧。但我們也能看到不論是何種語言,一些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)其設(shè)計(jì)思想都是相通的。掌握源碼背后的設(shè)計(jì)思路就可以舉一反三,不論是哪種新語言,新架構(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倍。

          送書活動(dòng)
          首先,感謝北京大學(xué)出版社為 "Java學(xué)習(xí)之道" 提供的書籍贊助,非常感謝!后續(xù)公眾號(hào)頭條推文,1周至少會(huì)有1-2次的文末送書活動(dòng),大家記得看完文章后,多多參與送書哈,混臉熟也能中獎(jiǎng)!

          《Java高并發(fā)編程指南》

          系統(tǒng):全書分為基礎(chǔ)、進(jìn)階、拓展和實(shí)戰(zhàn)四大篇,體系化講解Java高并發(fā)編程技術(shù)
          深入:深度剖析Java并發(fā)包、Dubbo等框架源碼設(shè)計(jì),領(lǐng)略大咖的代碼設(shè)計(jì)藝術(shù)
          實(shí)戰(zhàn):分布式系統(tǒng)設(shè)計(jì)理論與項(xiàng)目實(shí)戰(zhàn)相結(jié)合,懂理論,能落地,手把手教你吃透高并發(fā)項(xiàng)目核心技術(shù)
          資源:附贈(zèng)全書案例源代碼,知其然更知其所以然,快速上手不用愁


          可點(diǎn)擊下方鏈接直接購(gòu)買

          ?? 免費(fèi)獲取方法:

          6月9日前,公眾號(hào)后臺(tái)回復(fù) 【 并發(fā)學(xué)習(xí) 即可參與活動(dòng)!?。?/span>

                 
                              
          掃碼回復(fù)「并發(fā)學(xué)習(xí)」抽獎(jiǎng)品

          沒加小編微信的建議先加一下小編微信,方便中獎(jiǎng)之后安排發(fā)貨和領(lǐng)
                                        

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大鸡吧在线观看 | 精品秘 无码一区二区三区老师 | 婷婷丁香久久 | 亚洲.www| 黄片www在线观看 |