private void Insert(Object key, Object nvalue, bool add)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
}
Contract.EndContractBlock();
if (_count >= _loadsize)
{
expand();
}
else if (_occupancy > _loadsize && _count > 100)
{
rehash();
}
uint seed;
uint incr;
// Assume we only have one thread writing concurrently. Modify
// buckets to contain new data, as long as we insert in the right order.
uint hashcode = InitHash(key, _buckets.Length, out seed, out incr);
int ntry = 0;
int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots
// create by remove that have the collision bit set over using up new slots.
int bucketNumber = (int)(seed % (uint)_buckets.Length);
do
{
// Set emptySlot number to current bucket if it is the first available bucket that we have seen
// that once contained an entry and also has had a collision.
// We need to search this entire collision chain because we have to ensure that there are no
// duplicate entries in the table.
if (emptySlotNumber == -1 && (_buckets[bucketNumber].key == _buckets) && (_buckets[bucketNumber].hash_coll < 0))//(((buckets[bucketNumber].hash_coll & unchecked(0x80000000))!=0)))
emptySlotNumber = bucketNumber;
// Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry
// OR
// This bucket once contained an entry but there has never been a collision
if ((_buckets[bucketNumber].key == null) ||
(_buckets[bucketNumber].key == _buckets && ((_buckets[bucketNumber].hash_coll & unchecked(0x80000000)) == 0)))
{
// If we have found an available bucket that has never had a collision, but we've seen an available
// bucket in the past that has the collision bit set, use the previous bucket instead
if (emptySlotNumber != -1) // Reuse slot
bucketNumber = emptySlotNumber;
// We pretty much have to insert in this order. Don't set hash
// code until the value & key are set appropriately.
_isWriterInProgress = true;
_buckets[bucketNumber].val = nvalue;
_buckets[bucketNumber].key = key;
_buckets[bucketNumber].hash_coll |= (int)hashcode;
_count++;
UpdateVersion();
_isWriterInProgress = false;
#if FEATURE_RANDOMIZED_STRING_HASHING
if (ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
{
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
if (_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
{
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
rehash(buckets.Length, true);
}
}
#endif
return;
}
// The current bucket is in use
// OR
// it is available, but has had the collision bit set and we have already found an available bucket
if (((_buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) &&
KeyEquals(_buckets[bucketNumber].key, key))
{
if (add)
{
throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate__, _buckets[bucketNumber].key, key));
}
_isWriterInProgress = true;
_buckets[bucketNumber].val = nvalue;
UpdateVersion();
_isWriterInProgress = false;
#if FEATURE_RANDOMIZED_STRING_HASHING
if (ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
{
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
if (_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
{
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
rehash(buckets.Length, true);
}
}
#endif
return;
}
// The current bucket is full, and we have therefore collided. We need to set the collision bit
// unless we have remembered an available slot previously.
if (emptySlotNumber == -1)
{// We don't need to set the collision bit here since we already have an empty slot
if (_buckets[bucketNumber].hash_coll >= 0)
{
_buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
_occupancy++;
}
}
bucketNumber = (int)(((long)bucketNumber + incr) % (uint)_buckets.Length);
} while (++ntry < _buckets.Length);
// This code is here if and only if there were no buckets without a collision bit set in the entire table
if (emptySlotNumber != -1)
{
// We pretty much have to insert in this order. Don't set hash
// code until the value & key are set appropriately.
_isWriterInProgress = true;
_buckets[emptySlotNumber].val = nvalue;
_buckets[emptySlotNumber].key = key;
_buckets[emptySlotNumber].hash_coll |= (int)hashcode;
_count++;
UpdateVersion();
_isWriterInProgress = false;
#if FEATURE_RANDOMIZED_STRING_HASHING
if (buckets.Length > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
{
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
if (_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
{
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
rehash(buckets.Length, true);
}
}
#endif
return;
}
// If you see this assert, make sure load factor & count are reasonable.
// Then verify that our double hash function (h2, described at top of file)
// meets the requirements described above. You should never see this assert.
Debug.Fail("hash table insert failed! Load factor too high, or our double hashing function is incorrect.");
throw new InvalidOperationException(SR.InvalidOperation_HashInsertFailed);
}