前言
相信不少同學(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è)粗淺的答案叙凡。希望能幫到大家。
-
HashMap
和LinkedHashMap
有什么區(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)看一下源碼
使用HashMap
和LinkedHashMap
創(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ù)組+鏈表的形式嘛航缀。
初始化容量:
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)就是直接把key
的hashCode
和數(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文檔上看战虏,LinkedHashMap
和HashMap
的區(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ù)組按插入順序依次保存key
和value
。
用圖來(lái)表示就是下面這個(gè)樣子:
兩個(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;
...
}
查找操作
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)翠储,就將pair
和hashPattern
做異或,從前面的圖可知橡疼,這樣就得到了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ù)組,再將key
和value
緊挨著放入_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的HashMap
和LinkedHashMap
實(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è)問題連帶粗淺的答案一起放在下面:
HashMap
和LinkedHashMap
有什么區(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ù)組按插入順序依次保存key
和value
览徒。
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倍。