839 lines
No EOL
31 KiB
C#
839 lines
No EOL
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Serialization;
|
|
using JD.Kernel.StaticHelpers;
|
|
|
|
namespace JD.Collections.Details
|
|
{
|
|
/// <summary>
|
|
/// The base implementation for various collections classes that use hash tables
|
|
/// as part of their implementation. This class should not (and can not) be
|
|
/// used directly by end users; it's only for internal use by the collections package. The Hash
|
|
/// does not handle duplicate values.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The Hash manages items of type T, and uses a IComparer<ItemTYpe> that
|
|
/// hashes compares items to hash items into the table.
|
|
/// Adapted from PowerCollections.
|
|
///</remarks>
|
|
[Serializable]
|
|
internal class Hash<T> : IEnumerable<T>, ISerializable, IDeserializationCallback
|
|
{
|
|
#region Inner Types
|
|
|
|
/// <summary>
|
|
/// The structure that has each slot in the hash table. Each slot has three parts:
|
|
/// 1. The collision bit. Indicates whether some item visited this slot but had to
|
|
/// keep looking because the slot was full.
|
|
/// 2. 31-bit full hash value of the item. If zero, the slot is empty.
|
|
/// 3. The item itself.
|
|
/// </summary>
|
|
private struct Slot
|
|
{
|
|
private uint m_hash_collision; // Lower 31 bits: the hash value. Top bit: the collision bit.
|
|
public T Item; // The item.
|
|
|
|
/// <summary>
|
|
/// The full hash value associated with the value in this slot, or zero
|
|
/// if the slot is empty.
|
|
/// </summary>
|
|
public int HashValue
|
|
{
|
|
get
|
|
{
|
|
return (int)(m_hash_collision & 0x7FFFFFFF);
|
|
}
|
|
set
|
|
{
|
|
Debug.Assert((value & 0x80000000) == 0); // make sure sign bit isn't set.
|
|
m_hash_collision = (uint)value | (m_hash_collision & 0x80000000);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this slot empty?
|
|
/// </summary>
|
|
public bool Empty
|
|
{
|
|
get
|
|
{
|
|
return HashValue == 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears this slot, leaving the collision bit alone.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
HashValue = 0;
|
|
Item = default(T); // Done to avoid keeping things alive that shouldn't be.
|
|
}
|
|
|
|
/// <summary>
|
|
/// The "Collision" bit indicates that some value hit this slot and
|
|
/// collided, so had to try another slot.
|
|
/// </summary>
|
|
public bool Collision
|
|
{
|
|
get
|
|
{
|
|
return (m_hash_collision & 0x80000000) != 0;
|
|
}
|
|
set
|
|
{
|
|
if (value)
|
|
m_hash_collision |= 0x80000000;
|
|
else
|
|
m_hash_collision &= 0x7FFFFFFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Variables
|
|
|
|
private const int MINSIZE = 16; // minimum number of slots.
|
|
|
|
private IEqualityComparer<T> m_equalityComparer; // interface for comparing elements
|
|
|
|
private int m_count; // The number of elements in the table.
|
|
private int m_usedSlots; // Includes real elements and deleted elements with the collision bit on. Used to determine when we need to resize.
|
|
private int m_totalSlots; // Size of the table. Always a power of two.
|
|
private float m_loadFactor = 0.7f; // maximal load factor for the table.
|
|
private int m_thresholdGrow; // floor(totalSlots * loadFactor);
|
|
private int m_thresholdShrink; // thresholdGrow / 3.
|
|
private int m_hashMask; // Mask to convert hash values to the size of the table.
|
|
private int m_secondaryShift; // Shift to get the secondary skip value.
|
|
|
|
private Slot[] m_table; // The hash table.
|
|
|
|
private int m_changeStamp; // An integer that is changed every time the table structurally changes.
|
|
// Used so that enumerations throw an exception if the tree is changed
|
|
// during enumeration.
|
|
|
|
private SerializationInfo m_serializationInfo; // Info used during deserialization.
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor. Creates a new hash table.
|
|
/// </summary>
|
|
/// <param name="equalityComparer">The comparer to use to compare items. </param>
|
|
public Hash(IEqualityComparer<T> equalityComparer)
|
|
{
|
|
Debug.Assert(equalityComparer != null);
|
|
m_equalityComparer = equalityComparer;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets the number of items in the hash table.
|
|
/// </summary>
|
|
/// <value>The number of items stored in the hash table.</value>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return m_count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the number of slots in the hash table. Exposed internally
|
|
/// for testing purposes.
|
|
/// </summary>
|
|
/// <value>The number of slots in the hash table.</value>
|
|
internal int SlotCount
|
|
{
|
|
get
|
|
{
|
|
return m_totalSlots;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the load factor. Changing the load factor may cause
|
|
/// the size of the table to grow or shrink accordingly.
|
|
/// </summary>
|
|
/// <value></value>
|
|
public float LoadFactor
|
|
{
|
|
get
|
|
{
|
|
return m_loadFactor;
|
|
}
|
|
set
|
|
{
|
|
// Don't allow hopelessly inefficient load factors.
|
|
if (value < 0.25 || value > 0.95)
|
|
{
|
|
throw new ArgumentOutOfRangeException("LoadFactor", value, Strings.InvalidLoadFactor);
|
|
}
|
|
|
|
StopEnumerations();
|
|
|
|
bool maybeExpand = value < m_loadFactor; // May need to expand or shrink the table -- which?
|
|
|
|
// Update loadFactor and thresholds.
|
|
m_loadFactor = value;
|
|
m_thresholdGrow = (int)(m_totalSlots * m_loadFactor);
|
|
m_thresholdShrink = m_thresholdGrow / 3;
|
|
if (m_thresholdShrink <= MINSIZE)
|
|
m_thresholdShrink = 1;
|
|
|
|
// Possibly expand or shrink the table.
|
|
if (maybeExpand)
|
|
EnsureEnoughSlots(0);
|
|
else
|
|
ShrinkIfNeeded();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
#region Enumeration protection
|
|
|
|
/// <summary>
|
|
/// Gets the current enumeration stamp. Call CheckEnumerationStamp later
|
|
/// with this value to throw an exception if the hash table is changed.
|
|
/// </summary>
|
|
/// <returns>The current enumeration stamp.</returns>
|
|
internal int GetEnumerationStamp()
|
|
{
|
|
return m_changeStamp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Must be called whenever there is a structural change in the tree. Causes
|
|
/// changeStamp to be changed, which causes any in-progress enumerations
|
|
/// to throw exceptions.
|
|
/// </summary>
|
|
internal void StopEnumerations()
|
|
{
|
|
++m_changeStamp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the given stamp against the current change stamp. If different, the
|
|
/// collection has changed during enumeration and an <see cref="InvalidOperationException"/>
|
|
/// will be thrown.
|
|
/// </summary>
|
|
/// <param name="startStamp">changeStamp at the start of the enumeration.</param>
|
|
internal void CheckEnumerationStamp(int startStamp)
|
|
{
|
|
Throw.InvalidOperationIf(startStamp != m_changeStamp, Strings.ChangeDuringEnumeration);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private methods
|
|
|
|
/// <summary>
|
|
/// Gets the full hash code for an item.
|
|
/// </summary>
|
|
/// <param name="item">Item to get hash code for.</param>
|
|
/// <returns>The full hash code. It is never zero.</returns>
|
|
private int GetFullHash(T item)
|
|
{
|
|
uint hash = (uint)GetHashCode(item, m_equalityComparer);
|
|
|
|
// The .NET framework tends to produce pretty bad hash codes.
|
|
// Scramble them up to be much more random!
|
|
hash += ~(hash << 15);
|
|
hash ^= (hash >> 10);
|
|
hash += (hash << 3);
|
|
hash ^= (hash >> 6);
|
|
hash += ~(hash << 11);
|
|
hash ^= (hash >> 16);
|
|
hash &= 0x7FFFFFFF;
|
|
|
|
if (hash == 0)
|
|
{
|
|
hash = 0x7FFFFFFF; // Make sure it isn't zero.
|
|
}
|
|
|
|
return (int)hash;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get sthe initial bucket number and skips amount from the full hash value.
|
|
/// </summary>
|
|
/// <param name="hash">The full hash value.</param>
|
|
/// <param name="initialBucket">Returns the initial bucket. Always in the range 0..(totalSlots - 1).</param>
|
|
/// <param name="skip">Returns the skip values. Always odd in the range 0..(totalSlots - 1).</param>
|
|
private void GetHashValuesFromFullHash(int hash, out int initialBucket, out int skip)
|
|
{
|
|
initialBucket = hash & m_hashMask;
|
|
|
|
// The skip value must be relatively prime to the table size. Since the table size is a
|
|
// power of two, any odd number is relatively prime, so oring in 1 will do it.
|
|
skip = ((hash >> m_secondaryShift) & m_hashMask) | 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the full hash value, initial bucket number, and skip amount for an item.
|
|
/// </summary>
|
|
/// <param name="item">Item to get hash value of.</param>
|
|
/// <param name="initialBucket">Returns the initial bucket. Always in the range 0..(totalSlots - 1).</param>
|
|
/// <param name="skip">Returns the skip values. Always odd in the range 0..(totalSlots - 1).</param>
|
|
/// <returns>The full hash value. This is never zero.</returns>
|
|
private int GetHashValues(T item, out int initialBucket, out int skip)
|
|
{
|
|
int hash = GetFullHash(item);
|
|
GetHashValuesFromFullHash(hash, out initialBucket, out skip);
|
|
return hash;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes sure there are enough slots in the hash table so that <paramref name="additionalItems"/>
|
|
/// items can be inserted into the table.
|
|
/// </summary>
|
|
/// <param name="additionalItems">Number of additional items we are inserting.</param>
|
|
private void EnsureEnoughSlots(int additionalItems)
|
|
{
|
|
StopEnumerations();
|
|
|
|
if (m_usedSlots + additionalItems > m_thresholdGrow)
|
|
{
|
|
// We need to expand the table. Figure out to what size.
|
|
int newSize;
|
|
|
|
newSize = Math.Max(m_totalSlots, MINSIZE);
|
|
while ((int)(newSize * m_loadFactor) < m_usedSlots + additionalItems)
|
|
{
|
|
newSize *= 2;
|
|
Throw.InvalidOperationIf(newSize <= 0, Strings.CollectionTooLarge); // Must have overflowed the size of an int. Hard to believe we didn't run out of memory first.
|
|
}
|
|
|
|
ResizeTable(newSize);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the number of items in the table is small enough that
|
|
/// we should shrink the table again.
|
|
/// </summary>
|
|
private void ShrinkIfNeeded()
|
|
{
|
|
if (m_count < m_thresholdShrink)
|
|
{
|
|
int newSize;
|
|
|
|
if (m_count > 0)
|
|
{
|
|
newSize = MINSIZE;
|
|
while ((int)(newSize * m_loadFactor) < m_count)
|
|
newSize *= 2;
|
|
}
|
|
else
|
|
{
|
|
// We've removed all the elements. Shrink to zero.
|
|
newSize = 0;
|
|
}
|
|
|
|
ResizeTable(newSize);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given the size of a hash table, computes the "secondary shift" value -- the shift
|
|
/// that is used to determine the skip amount for collision resolution.
|
|
/// </summary>
|
|
/// <param name="newSize">The new size of the table.</param>
|
|
/// <returns>The secondary skip amount.</returns>
|
|
private int GetSecondaryShift(int newSize)
|
|
{
|
|
int x = newSize - 2; // x is of the form 0000111110 -- a single string of 1's followed by a single zero.
|
|
int secondaryShift = 0;
|
|
|
|
// Keep shifting x until it is the set of bits we want to extract: it be the highest bits possible,
|
|
// but can't overflow into the sign bit.
|
|
while ((x & 0x40000000) == 0)
|
|
{
|
|
x <<= 1;
|
|
++secondaryShift;
|
|
}
|
|
|
|
return secondaryShift;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resize the hash table to the given new size, moving all items into the
|
|
/// new hash table.
|
|
/// </summary>
|
|
/// <param name="newSize">The new size of the hash table. Must be a power
|
|
/// of two.</param>
|
|
private void ResizeTable(int newSize)
|
|
{
|
|
Slot[] oldTable = m_table; // Move all the items from this table to the new table.
|
|
|
|
Debug.Assert((newSize & (newSize - 1)) == 0); // Check newSize is a power of two.
|
|
|
|
m_totalSlots = newSize;
|
|
m_thresholdGrow = (int)(m_totalSlots * m_loadFactor);
|
|
m_thresholdShrink = m_thresholdGrow / 3;
|
|
|
|
if (m_thresholdShrink <= MINSIZE)
|
|
{
|
|
m_thresholdShrink = 1;
|
|
}
|
|
|
|
m_hashMask = newSize - 1;
|
|
m_secondaryShift = GetSecondaryShift(newSize);
|
|
|
|
if (m_totalSlots > 0)
|
|
{
|
|
m_table = new Slot[m_totalSlots];
|
|
}
|
|
else
|
|
{
|
|
m_table = null;
|
|
}
|
|
|
|
if (oldTable != null && m_table != null)
|
|
{
|
|
foreach (Slot oldSlot in oldTable)
|
|
{
|
|
int hash, bucket, skip;
|
|
|
|
hash = oldSlot.HashValue;
|
|
GetHashValuesFromFullHash(hash, out bucket, out skip);
|
|
|
|
// Find an empty bucket.
|
|
while (!m_table[bucket].Empty)
|
|
{
|
|
// The slot is used, but isn't our item. Set the collision bit and keep looking.
|
|
m_table[bucket].Collision = true;
|
|
bucket = (bucket + skip) & m_hashMask;
|
|
}
|
|
|
|
// We found an empty bucket.
|
|
m_table[bucket].HashValue = hash;
|
|
m_table[bucket].Item = oldSlot.Item;
|
|
}
|
|
}
|
|
|
|
m_usedSlots = m_count; // no deleted elements have the collision bit on now.
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public methods
|
|
|
|
/// <summary>
|
|
/// Inserts a new item into the hash table. If a duplicate item exists, can replace or do nothing.
|
|
/// </summary>
|
|
/// <param name="item">The item to insert.</param>
|
|
/// <param name="replaceOnDuplicate">If true, duplicate items are replaced. If false, nothing is done if a duplicate already exists.</param>
|
|
/// <param name="previous">If a duplicate was found, returns it (whether replaced or not).</param>
|
|
/// <returns>True if no duplicate existed, false if a duplicate was found.</returns>
|
|
public bool Insert(T item, bool replaceOnDuplicate, out T previous)
|
|
{
|
|
int hash, bucket, skip;
|
|
int emptyBucket = -1; // If >= 0, an empty bucket we can use for a true insert
|
|
bool duplicateMightExist = true; // If true, still the possibility that a duplicate exists.
|
|
|
|
EnsureEnoughSlots(1); // Ensure enough room to insert. Also stops enumerations.
|
|
|
|
hash = GetHashValues(item, out bucket, out skip);
|
|
|
|
for (; ; )
|
|
{
|
|
if (m_table[bucket].Empty)
|
|
{
|
|
// Record the location of the first empty bucket seen. This is where the item will
|
|
// go if no duplicate exists.
|
|
if (emptyBucket == -1)
|
|
emptyBucket = bucket;
|
|
|
|
if (!duplicateMightExist || !m_table[bucket].Collision)
|
|
{
|
|
// There can't be a duplicate further on, because a bucket with the collision bit
|
|
// clear was found (here or earlier). We have the place to insert.
|
|
break;
|
|
}
|
|
}
|
|
else if (m_table[bucket].HashValue == hash && m_equalityComparer.Equals(m_table[bucket].Item, item))
|
|
{
|
|
// We found a duplicate item. Replace it if requested to.
|
|
previous = m_table[bucket].Item;
|
|
if (replaceOnDuplicate)
|
|
{
|
|
m_table[bucket].Item = item;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// The slot is used, but isn't our item.
|
|
if (!m_table[bucket].Collision)
|
|
{
|
|
// Since the collision bit is off, we can't have a duplicate.
|
|
if (emptyBucket >= 0)
|
|
{
|
|
// We already have an empty bucket to use.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Keep searching for an empty bucket to place the item.
|
|
m_table[bucket].Collision = true;
|
|
duplicateMightExist = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bucket = (bucket + skip) & m_hashMask;
|
|
}
|
|
|
|
// We found an empty bucket. Insert the new item.
|
|
m_table[emptyBucket].HashValue = hash;
|
|
m_table[emptyBucket].Item = item;
|
|
|
|
++m_count;
|
|
if (!m_table[emptyBucket].Collision)
|
|
{
|
|
++m_usedSlots;
|
|
}
|
|
|
|
previous = default(T);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes an item from the hash table.
|
|
/// </summary>
|
|
/// <param name="item">Item to search for and delete.</param>
|
|
/// <param name="itemDeleted">If true returned, the actual item stored in the hash table (must be equal to <paramref name="item"/>, but may not be identical).</param>
|
|
/// <returns>True if item was found and deleted, false if item wasn't found.</returns>
|
|
public bool Delete(T item, out T itemDeleted)
|
|
{
|
|
int hash, bucket, skip;
|
|
|
|
StopEnumerations();
|
|
|
|
if (m_count == 0)
|
|
{
|
|
itemDeleted = default(T);
|
|
return false;
|
|
}
|
|
|
|
hash = GetHashValues(item, out bucket, out skip);
|
|
|
|
for (; ; )
|
|
{
|
|
if (m_table[bucket].HashValue == hash && m_equalityComparer.Equals(m_table[bucket].Item, item))
|
|
{
|
|
// Found the item. Remove it.
|
|
itemDeleted = m_table[bucket].Item;
|
|
m_table[bucket].Clear();
|
|
--m_count;
|
|
if (!m_table[bucket].Collision)
|
|
--m_usedSlots;
|
|
ShrinkIfNeeded();
|
|
return true;
|
|
}
|
|
else if (!m_table[bucket].Collision)
|
|
{
|
|
// No collision bit, so we can stop searching. No such element.
|
|
itemDeleted = default(T);
|
|
return false;
|
|
}
|
|
|
|
bucket = (bucket + skip) & m_hashMask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds an item in the hash table. If found, optionally replace it with the
|
|
/// finding item.
|
|
/// </summary>
|
|
/// <param name="find">Item to find.</param>
|
|
/// <param name="replace">If true, replaces the equal item in the hash table
|
|
/// with <paramref name="item"/>.</param>
|
|
/// <param name="item">Returns the equal item found in the table, if true was returned.</param>
|
|
/// <returns>True if the item was found, false otherwise.</returns>
|
|
public bool Find(T find, bool replace, out T item)
|
|
{
|
|
int hash, bucket, skip;
|
|
|
|
if (m_count == 0)
|
|
{
|
|
item = default(T);
|
|
return false;
|
|
}
|
|
|
|
hash = GetHashValues(find, out bucket, out skip);
|
|
|
|
for (; ; )
|
|
{
|
|
if (m_table[bucket].HashValue == hash && m_equalityComparer.Equals(m_table[bucket].Item, find))
|
|
{
|
|
// Found the item.
|
|
item = m_table[bucket].Item;
|
|
if (replace)
|
|
m_table[bucket].Item = find;
|
|
return true;
|
|
}
|
|
else if (!m_table[bucket].Collision)
|
|
{
|
|
// No collision bit, so we can stop searching. No such element.
|
|
item = default(T);
|
|
return false;
|
|
}
|
|
|
|
bucket = (bucket + skip) & m_hashMask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate all of the items in the hash table. The items are enumerated in a haphazard, unpredictable order.
|
|
/// </summary>
|
|
/// <returns>An IEnumerator<T> that enumerates the items in the hash table.</returns>
|
|
public IEnumerator<T> GetEnumerator()
|
|
{
|
|
if (m_count > 0)
|
|
{
|
|
int startStamp = m_changeStamp;
|
|
|
|
foreach (Slot slot in m_table)
|
|
{
|
|
if (!slot.Empty)
|
|
{
|
|
yield return slot.Item;
|
|
CheckEnumerationStamp(startStamp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerate all of the items in the hash table. The items
|
|
/// are enumerated in a haphazard, unpredictable order.
|
|
/// </summary>
|
|
/// <returns>An IEnumerator that enumerates the items
|
|
/// in the hash table.</returns>
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a shallow clone of this hash table.
|
|
/// </summary>
|
|
/// <param name="cloneFunc">If non-null, this function is applied to each item when cloning. It must be the case that this function does not modify the hash code or equality function.</param>
|
|
/// <returns>A shallow clone that contains the same items.</returns>
|
|
public Hash<T> Clone(Func<T, T> cloneFunc)
|
|
{
|
|
Hash<T> clone = new Hash<T>(m_equalityComparer);
|
|
clone.m_count = this.m_count;
|
|
clone.m_usedSlots = this.m_usedSlots;
|
|
clone.m_totalSlots = this.m_totalSlots;
|
|
clone.m_loadFactor = this.m_loadFactor;
|
|
clone.m_thresholdGrow = this.m_thresholdGrow;
|
|
clone.m_thresholdShrink = this.m_thresholdShrink;
|
|
clone.m_hashMask = this.m_hashMask;
|
|
clone.m_secondaryShift = this.m_secondaryShift;
|
|
if (m_table != null)
|
|
{
|
|
clone.m_table = (Slot[])m_table.Clone();
|
|
|
|
if (cloneFunc != null)
|
|
{
|
|
for (int i = 0; i < m_table.Length; ++i)
|
|
{
|
|
if (!m_table[i].Empty)
|
|
m_table[i].Item = cloneFunc(m_table[i].Item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the hash code for an object using a comparer. Correctly handles null.
|
|
/// </summary>
|
|
/// <param name="item">Item to get hash code for. Can be null.</param>
|
|
/// <param name="equalityComparer">The comparer to use.</param>
|
|
/// <returns>The hash code for the item.</returns>
|
|
internal static int GetHashCode(T item, IEqualityComparer<T> equalityComparer)
|
|
{
|
|
Debug.Assert(equalityComparer != null);
|
|
|
|
if (ReferenceEquals(item, null))
|
|
{
|
|
return 0x1786E23C;
|
|
}
|
|
|
|
return equalityComparer.GetHashCode(item);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Serialization
|
|
|
|
/// <summary>
|
|
/// Serialize the hash table. Called from the serialization infrastructure.
|
|
/// </summary>
|
|
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
throw new ArgumentNullException("info");
|
|
|
|
info.AddValue("equalityComparer", m_equalityComparer, typeof(IEqualityComparer<T>));
|
|
info.AddValue("loadFactor", m_loadFactor, typeof(float));
|
|
T[] items = new T[m_count];
|
|
|
|
if (m_table != null)
|
|
{
|
|
int i = 0;
|
|
foreach (Slot slot in m_table)
|
|
{
|
|
if (!slot.Empty)
|
|
{
|
|
items[i++] = slot.Item;
|
|
}
|
|
}
|
|
}
|
|
|
|
info.AddValue("items", items, typeof(T[]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called on deserialization. We cannot deserialize now, because hash codes
|
|
/// might not be correct now. We do real deserialization in the OnDeserialization call.
|
|
/// </summary>
|
|
protected Hash(SerializationInfo serInfo, StreamingContext context)
|
|
{
|
|
// Save away the serialization info for use later. We can't be sure of hash codes
|
|
// being stable until the entire object graph is deserialized, so we wait until then
|
|
// to deserialize.
|
|
m_serializationInfo = serInfo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserialize the hash table. Called from the serialization infrastructure when
|
|
/// the object graph has finished deserializing.
|
|
/// </summary>
|
|
void IDeserializationCallback.OnDeserialization(object sender)
|
|
{
|
|
if (m_serializationInfo == null)
|
|
return;
|
|
|
|
m_loadFactor = m_serializationInfo.GetSingle("loadFactor");
|
|
m_equalityComparer = (IEqualityComparer<T>)m_serializationInfo.GetValue("equalityComparer", typeof(IEqualityComparer<T>));
|
|
|
|
T[] items = (T[])m_serializationInfo.GetValue("items", typeof(T[]));
|
|
T dummy;
|
|
|
|
EnsureEnoughSlots(items.Length);
|
|
foreach (T item in items)
|
|
Insert(item, true, out dummy);
|
|
|
|
m_serializationInfo = null;
|
|
}
|
|
|
|
#endregion Serialization
|
|
|
|
#region Debug methods
|
|
|
|
/// <summary>
|
|
/// Prints out basic stats about the hash table.
|
|
/// </summary>
|
|
[Conditional("DEBUG")]
|
|
internal void PrintStats()
|
|
{
|
|
Console.WriteLine("count={0} usedSlots={1} totalSlots={2}", m_count, m_usedSlots, m_totalSlots);
|
|
Console.WriteLine("loadFactor={0} thresholdGrow={1} thresholdShrink={2}", m_loadFactor, m_thresholdGrow, m_thresholdShrink);
|
|
Console.WriteLine("hashMask={0:X} secondaryShift={1}", m_hashMask, m_secondaryShift);
|
|
Console.WriteLine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prints out the state of the hash table and each of the slots. Each slot looks like:
|
|
/// Slot 4: C 4513e41e hello
|
|
/// where the "C" indicates the collision bit is on
|
|
/// the next hex number is the hash value
|
|
/// followed by ToString() on the item.
|
|
/// </summary>
|
|
[Conditional("DEBUG")]
|
|
internal void Print()
|
|
{
|
|
PrintStats();
|
|
for (int i = 0; i < m_totalSlots; ++i)
|
|
Console.WriteLine("Slot {0,4:X}: {1} {2,8:X} {3}", i, m_table[i].Collision ? "C" : " ",
|
|
m_table[i].HashValue, m_table[i].Empty ? "<empty>" : m_table[i].Item.ToString());
|
|
Console.WriteLine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks that everything appears to be OK in the hash table.
|
|
/// </summary>
|
|
[Conditional("DEBUG")]
|
|
internal void Validate()
|
|
{
|
|
Debug.Assert(m_count <= m_usedSlots);
|
|
Debug.Assert(m_count <= m_totalSlots);
|
|
Debug.Assert(m_usedSlots <= m_totalSlots);
|
|
Debug.Assert(m_usedSlots <= m_thresholdGrow);
|
|
Debug.Assert((int)(m_totalSlots * m_loadFactor) == m_thresholdGrow);
|
|
if (m_thresholdShrink > 1)
|
|
Debug.Assert(m_thresholdGrow / 3 == m_thresholdShrink);
|
|
else
|
|
Debug.Assert(m_thresholdGrow / 3 <= MINSIZE);
|
|
if (m_totalSlots > 0)
|
|
{
|
|
Debug.Assert((m_totalSlots & (m_totalSlots - 1)) == 0); // totalSlots is a power of two.
|
|
Debug.Assert(m_totalSlots - 1 == m_hashMask);
|
|
Debug.Assert(GetSecondaryShift(m_totalSlots) == m_secondaryShift);
|
|
Debug.Assert(m_totalSlots == m_table.Length);
|
|
}
|
|
|
|
// Traverse the table. Make sure that count and usedSlots are right, and that
|
|
// each slot looks reasonable.
|
|
int expectedCount = 0, expectedUsed = 0, initialBucket = 0, skip;
|
|
if (m_table != null)
|
|
{
|
|
for (int i = 0; i < m_totalSlots; ++i)
|
|
{
|
|
Slot slot = m_table[i];
|
|
if (slot.Empty)
|
|
{
|
|
// Empty slot
|
|
if (slot.Collision)
|
|
++expectedUsed;
|
|
Debug.Assert(object.Equals(default(T), slot.Item));
|
|
}
|
|
else
|
|
{
|
|
// not empty.
|
|
++expectedCount;
|
|
++expectedUsed;
|
|
Debug.Assert(slot.HashValue != 0);
|
|
Debug.Assert(GetHashValues(slot.Item, out initialBucket, out skip) == slot.HashValue);
|
|
if (initialBucket != i)
|
|
Debug.Assert(m_table[initialBucket].Collision);
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.Assert(expectedCount == m_count);
|
|
Debug.Assert(expectedUsed == m_usedSlots);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|
|
} |