2 // System.Collections.Hashtable.cs
5 // Sergey Chaban (serge@wildwestsoftware.com)
9 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
33 using System.Runtime.Serialization;
36 using System.Runtime.ConstrainedExecution;
37 using System.Runtime.InteropServices;
40 namespace System.Collections {
46 public class Hashtable : IDictionary, ICollection,
47 IEnumerable, ICloneable, ISerializable, IDeserializationCallback
51 internal struct Slot {
54 internal Object value;
56 // Hashcode. Chains are also marked through this.
61 internal class KeyMarker: IObjectReference
63 public readonly static KeyMarker Removed = new KeyMarker();
64 public object GetRealObject (StreamingContext context)
65 { return KeyMarker.Removed; }
72 const int CHAIN_MARKER = ~Int32.MaxValue;
76 private int modificationCount;
77 private float loadFactor;
78 private Slot [] table;
79 private int threshold;
81 private HashKeys hashKeys;
82 private HashValues hashValues;
84 private IHashCodeProvider hcpRef;
85 private IComparer comparerRef;
86 private SerializationInfo serializationInfo;
89 private IEqualityComparer equalityComparer;
92 private static readonly int [] primeTbl = {
133 public Hashtable () : this (0, 1.0f) {}
136 [Obsolete ("Please use Hashtable(int, float, IEqualityComparer) instead")]
138 public Hashtable (int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
140 throw new ArgumentOutOfRangeException ("capacity", "negative capacity");
142 if (loadFactor < 0.1f || loadFactor > 1.0f || Single.IsNaN (loadFactor))
143 throw new ArgumentOutOfRangeException ("loadFactor", "load factor");
145 if (capacity == 0) ++capacity;
146 this.loadFactor = 0.75f*loadFactor;
147 double tableSize = capacity / this.loadFactor;
149 if (tableSize > Int32.MaxValue)
150 throw new ArgumentException ("Size is too big");
152 int size = (int) tableSize;
153 size = ToPrime (size);
154 this.SetTable (new Slot [size]);
157 this.comparer = comparer;
160 this.modificationCount = 0;
163 public Hashtable (int capacity, float loadFactor) :
164 this (capacity, loadFactor, null, null)
168 public Hashtable (int capacity) : this (capacity, 1.0f)
173 [Obsolete ("Please use Hashtable(int, IEqualityComparer) instead")]
175 public Hashtable (int capacity,
176 IHashCodeProvider hcp,
178 : this (capacity, 1.0f, hcp, comparer)
183 [Obsolete ("Please use Hashtable(IDictionary, float, IEqualityComparer) instead")]
185 public Hashtable (IDictionary d, float loadFactor,
186 IHashCodeProvider hcp, IComparer comparer)
187 : this (d!=null ? d.Count : 0,
188 loadFactor, hcp, comparer)
192 throw new ArgumentNullException ("dictionary");
194 IDictionaryEnumerator it = d.GetEnumerator ();
195 while (it.MoveNext ()) {
196 Add (it.Key, it.Value);
201 public Hashtable (IDictionary d, float loadFactor)
202 : this (d, loadFactor, null, null)
207 public Hashtable (IDictionary d) : this (d, 1.0f)
212 [Obsolete ("Please use Hashtable(IDictionary, IEqualityComparer) instead")]
214 public Hashtable (IDictionary d, IHashCodeProvider hcp, IComparer comparer)
215 : this (d, 1.0f, hcp, comparer)
220 [Obsolete ("Please use Hashtable(IEqualityComparer) instead")]
222 public Hashtable (IHashCodeProvider hcp, IComparer comparer)
223 : this (1, 1.0f, hcp, comparer)
227 protected Hashtable (SerializationInfo info, StreamingContext context)
229 serializationInfo = info;
233 public Hashtable (IDictionary d, IEqualityComparer equalityComparer) : this (d, 1.0f, equalityComparer)
237 public Hashtable (IDictionary d, float loadFactor, IEqualityComparer equalityComparer) : this (d, loadFactor)
239 this.equalityComparer = equalityComparer;
242 public Hashtable (IEqualityComparer equalityComparer) : this (1, 1.0f, equalityComparer)
246 public Hashtable (int capacity, IEqualityComparer equalityComparer) : this (capacity, 1.0f, equalityComparer)
250 public Hashtable (int capacity, float loadFactor, IEqualityComparer equalityComparer) : this (capacity, loadFactor)
252 this.equalityComparer = equalityComparer;
261 [Obsolete ("Please use EqualityComparer property.")]
263 protected IComparer comparer {
273 [Obsolete ("Please use EqualityComparer property.")]
275 protected IHashCodeProvider hcp {
285 protected IEqualityComparer EqualityComparer {
287 return equalityComparer;
294 public virtual int Count {
300 public virtual bool IsSynchronized {
306 public virtual Object SyncRoot {
316 public virtual bool IsFixedSize {
323 public virtual bool IsReadOnly {
329 public virtual ICollection Keys {
331 if (this.hashKeys == null)
332 this.hashKeys = new HashKeys (this);
333 return this.hashKeys;
337 public virtual ICollection Values {
339 if (this.hashValues == null)
340 this.hashValues = new HashValues (this);
341 return this.hashValues;
347 public virtual Object this [Object key] {
350 throw new ArgumentNullException ("key", "null key");
352 Slot [] table = this.table;
353 uint size = (uint) table.Length;
354 int h = this.GetHash (key) & Int32.MaxValue;
356 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
359 for (uint i = size; i > 0; i--) {
361 Slot entry = table [indx];
362 Object k = entry.key;
366 if (k == key || ((entry.hashMix & Int32.MaxValue) == h
367 && this.KeyEquals (key, k))) {
371 if ((entry.hashMix & CHAIN_MARKER) == 0)
381 PutImpl (key, value, true);
395 IEnumerator IEnumerable.GetEnumerator ()
397 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
402 public virtual void CopyTo (Array array, int arrayIndex)
405 throw new ArgumentNullException ("array");
408 throw new ArgumentOutOfRangeException ("arrayIndex");
411 throw new ArgumentException ("array is multidimensional");
413 if ((array.Length > 0) && (arrayIndex >= array.Length))
414 throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
416 if (arrayIndex + this.inUse > array.Length)
417 throw new ArgumentException ("Not enough room from arrayIndex to end of array for this Hashtable");
419 IDictionaryEnumerator it = GetEnumerator ();
422 while (it.MoveNext ()) {
423 array.SetValue (it.Entry, i++);
430 public virtual void Add (Object key, Object value)
432 PutImpl (key, value, false);
436 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.Success)]
438 public virtual void Clear ()
440 for (int i = 0;i<table.Length;i++) {
441 table [i].key = null;
442 table [i].value = null;
443 table [i].hashMix = 0;
450 public virtual bool Contains (Object key)
452 return (Find (key) >= 0);
455 public virtual IDictionaryEnumerator GetEnumerator ()
457 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
461 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)]
463 public virtual void Remove (Object key)
467 Slot [] table = this.table;
468 int h = table [i].hashMix;
470 table [i].hashMix = h;
471 table [i].key = (h != 0)
474 table [i].value = null;
480 public virtual bool ContainsKey (object key)
482 return Contains (key);
485 public virtual bool ContainsValue (object value)
487 int size = this.table.Length;
488 Slot [] table = this.table;
490 for (int i = 0; i < size; i++) {
491 Slot entry = table [i];
492 if (entry.key != null && entry.key!= KeyMarker.Removed
493 && entry.value == null) {
498 for (int i = 0; i < size; i++) {
499 Slot entry = table [i];
500 if (entry.key != null && entry.key!= KeyMarker.Removed
501 && value.Equals (entry.value)) {
512 public virtual object Clone ()
515 Hashtable ht = new Hashtable (Count, equalityComparer);
517 ht.comparer = this.comparer;
519 Hashtable ht = new Hashtable (Count, hcp, comparer);
522 ht.loadFactor = this.loadFactor;
524 // FIXME: maybe it's faster to simply
525 // copy the back-end array?
527 IDictionaryEnumerator it = GetEnumerator ();
528 while (it.MoveNext ()) {
529 ht [it.Key] = it.Value;
535 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
538 throw new ArgumentNullException ("info");
540 info.AddValue ("LoadFactor", loadFactor);
541 info.AddValue ("Version", modificationCount);
543 if (equalityComparer != null)
544 info.AddValue ("KeyComparer", equalityComparer);
546 info.AddValue ("Comparer", comparerRef);
548 info.AddValue ("Comparer", comparerRef);
550 info.AddValue ("HashCodeProvider", hcpRef);
551 info.AddValue ("HashSize", this.table.Length);
553 Object [] keys = new Object[inUse];
554 CopyToArray(keys, 0, EnumeratorMode.KEY_MODE);
557 Object [] values = new Object[inUse];
558 CopyToArray(values, 0, EnumeratorMode.VALUE_MODE);
560 info.AddValue ("Keys", keys);
561 info.AddValue ("Values", values);
564 info.AddValue ("equalityComparer", equalityComparer);
569 [MonoTODO ("Serialize equalityComparer")]
571 public virtual void OnDeserialization (object sender)
573 if (serializationInfo == null) return;
575 loadFactor = (float) serializationInfo.GetValue ("LoadFactor", typeof(float));
576 modificationCount = (int) serializationInfo.GetValue ("Version", typeof(int));
579 equalityComparer = (IEqualityComparer) serializationInfo.GetValue ("KeyComparer", typeof (object));
581 // If not found, try to get "Comparer"
584 if (equalityComparer == null)
585 comparerRef = (IComparer) serializationInfo.GetValue ("Comparer", typeof (object));
587 comparerRef = (IComparer) serializationInfo.GetValue ("Comparer", typeof (object));
590 hcpRef = (IHashCodeProvider) serializationInfo.GetValue ("HashCodeProvider", typeof (object));
591 int size = (int) serializationInfo.GetValue ("HashSize", typeof(int));
593 Object [] keys = (Object []) serializationInfo.GetValue("Keys", typeof(Object [] ));
594 Object [] values = (Object []) serializationInfo.GetValue("Values", typeof(Object [] ));
596 if (keys.Length != values.Length)
597 throw new SerializationException("Keys and values of uneven size");
599 size = ToPrime (size);
600 this.SetTable (new Slot [size]);
602 for(int i=0;i<keys.Length;i++)
603 Add(keys[i], values[i]);
607 serializationInfo = null;
611 /// Returns a synchronized (thread-safe)
612 /// wrapper for the Hashtable.
614 public static Hashtable Synchronized (Hashtable table)
617 throw new ArgumentNullException("table");
618 return new SyncHashtable (table);
624 // Protected instance methods
627 /// <summary>Returns the hash code for the specified key.</summary>
628 protected virtual int GetHash (Object key)
631 if (equalityComparer != null)
632 return equalityComparer.GetHashCode (key);
635 return key.GetHashCode ();
637 return hcpRef.GetHashCode (key);
641 /// Compares a specific Object with a specific key
642 /// in the Hashtable.
644 protected virtual bool KeyEquals (Object item, Object key)
647 if (equalityComparer != null)
648 return equalityComparer.Equals (item, key);
650 if (comparerRef == null)
651 return item.Equals (key);
653 return comparerRef.Compare (item, key) == 0;
659 // Private instance methods
662 private void AdjustThreshold ()
664 int size = table.Length;
666 threshold = (int) (size*loadFactor);
667 if (this.threshold >= size)
671 private void SetTable (Slot [] table)
674 throw new ArgumentNullException ("table");
680 private int Find (Object key)
683 throw new ArgumentNullException ("key", "null key");
685 Slot [] table = this.table;
686 uint size = (uint) table.Length;
687 int h = this.GetHash (key) & Int32.MaxValue;
689 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
692 for (uint i = size; i > 0; i--) {
694 Slot entry = table [indx];
695 Object k = entry.key;
699 if (k == key || ((entry.hashMix & Int32.MaxValue) == h
700 && this.KeyEquals (key, k))) {
704 if ((entry.hashMix & CHAIN_MARKER) == 0)
713 private void Rehash ()
715 int oldSize = this.table.Length;
717 // From the SDK docs:
718 // Hashtable is automatically increased
719 // to the smallest prime number that is larger
720 // than twice the current number of Hashtable buckets
721 uint newSize = (uint)ToPrime ((oldSize<<1)|1);
724 Slot [] newTable = new Slot [newSize];
725 Slot [] table = this.table;
727 for (int i = 0;i<oldSize;i++) {
730 int h = s.hashMix & Int32.MaxValue;
732 uint step = ((uint) (h>>5)+1)% (newSize-1)+1;
733 for (uint j = spot%newSize;;spot+= step, j = spot%newSize) {
734 // No check for KeyMarker.Removed here,
735 // because the table is just allocated.
736 if (newTable [j].key == null) {
737 newTable [j].key = s.key;
738 newTable [j].value = s.value;
739 newTable [j].hashMix |= h;
742 newTable [j].hashMix |= CHAIN_MARKER;
748 ++this.modificationCount;
750 this.SetTable (newTable);
754 private void PutImpl (Object key, Object value, bool overwrite)
757 throw new ArgumentNullException ("key", "null key");
759 if (this.inUse >= this.threshold)
762 uint size = (uint)this.table.Length;
764 int h = this.GetHash (key) & Int32.MaxValue;
766 uint step = (uint) ((spot>>5)+1)% (size-1)+1;
767 Slot [] table = this.table;
771 for (int i = 0; i < size; i++) {
772 int indx = (int) (spot % size);
773 entry = table [indx];
776 && entry.key == KeyMarker.Removed
777 && (entry.hashMix & CHAIN_MARKER) != 0)
780 if (entry.key == null ||
781 (entry.key == KeyMarker.Removed
782 && (entry.hashMix & CHAIN_MARKER) == 0)) {
789 if ((entry.hashMix & Int32.MaxValue) == h && KeyEquals (key, entry.key)) {
791 table [indx].value = value;
792 ++this.modificationCount;
795 // An entry with the same key already exists in the Hashtable.
796 throw new ArgumentException (
797 "Key duplication when adding: " + key);
802 if (freeIndx == -1) {
803 table [indx].hashMix |= CHAIN_MARKER;
811 table [freeIndx].key = key;
812 table [freeIndx].value = value;
813 table [freeIndx].hashMix |= h;
816 ++this.modificationCount;
821 private void CopyToArray (Array arr, int i,
824 IEnumerator it = new Enumerator (this, mode);
826 while (it.MoveNext ()) {
827 arr.SetValue (it.Current, i++);
834 // Private static methods
836 internal static bool TestPrime (int x)
839 int top = (int)Math.Sqrt (x);
841 for (int n = 3; n < top; n += 2) {
847 // There is only one even prime - 2.
851 internal static int CalcPrime (int x)
853 for (int i = (x & (~1))-1; i< Int32.MaxValue; i += 2) {
854 if (TestPrime (i)) return i;
859 internal static int ToPrime (int x)
861 for (int i = 0; i < primeTbl.Length; i++) {
862 if (x <= primeTbl [i])
865 return CalcPrime (x);
875 private enum EnumeratorMode : int {KEY_MODE = 0, VALUE_MODE, ENTRY_MODE};
878 private sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
880 private Hashtable host;
884 private EnumeratorMode mode;
886 private Object currentKey;
887 private Object currentValue;
889 private readonly static string xstr = "Hashtable.Enumerator: snapshot out of sync.";
891 public Enumerator (Hashtable host, EnumeratorMode mode) {
893 stamp = host.modificationCount;
894 size = host.table.Length;
899 public Enumerator (Hashtable host)
900 : this (host, EnumeratorMode.KEY_MODE) {}
903 private void FailFast ()
905 if (host.modificationCount != stamp) {
906 throw new InvalidOperationException (xstr);
919 public bool MoveNext ()
924 while (++pos < size) {
925 Slot entry = host.table [pos];
927 if (entry.key != null && entry.key != KeyMarker.Removed) {
928 currentKey = entry.key;
929 currentValue = entry.value;
940 public DictionaryEntry Entry
943 if (currentKey == null) throw new InvalidOperationException ();
945 return new DictionaryEntry (currentKey, currentValue);
951 if (currentKey == null) throw new InvalidOperationException ();
957 public Object Value {
959 if (currentKey == null) throw new InvalidOperationException ();
965 public Object Current {
967 if (currentKey == null) throw new InvalidOperationException ();
970 case EnumeratorMode.KEY_MODE:
972 case EnumeratorMode.VALUE_MODE:
974 case EnumeratorMode.ENTRY_MODE:
975 return new DictionaryEntry (currentKey, currentValue);
977 throw new Exception ("should never happen");
983 private class HashKeys : ICollection, IEnumerable {
985 private Hashtable host;
987 public HashKeys (Hashtable host) {
989 throw new ArgumentNullException ();
996 public virtual int Count {
1002 public virtual bool IsSynchronized {
1004 return host.IsSynchronized;
1008 public virtual Object SyncRoot {
1009 get {return host.SyncRoot;}
1012 public virtual void CopyTo (Array array, int arrayIndex)
1015 throw new ArgumentNullException ("array");
1016 if (array.Rank != 1)
1017 throw new ArgumentException ("array");
1019 throw new ArgumentOutOfRangeException ("arrayIndex");
1020 if (array.Length - arrayIndex < Count)
1021 throw new ArgumentException ("not enough space");
1023 host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE);
1028 public virtual IEnumerator GetEnumerator ()
1030 return new Hashtable.Enumerator (host, EnumeratorMode.KEY_MODE);
1035 private class HashValues : ICollection, IEnumerable {
1037 private Hashtable host;
1039 public HashValues (Hashtable host) {
1041 throw new ArgumentNullException ();
1048 public virtual int Count {
1054 public virtual bool IsSynchronized {
1056 return host.IsSynchronized;
1060 public virtual Object SyncRoot {
1062 return host.SyncRoot;
1066 public virtual void CopyTo (Array array, int arrayIndex)
1069 throw new ArgumentNullException ("array");
1070 if (array.Rank != 1)
1071 throw new ArgumentException ("array");
1073 throw new ArgumentOutOfRangeException ("arrayIndex");
1074 if (array.Length - arrayIndex < Count)
1075 throw new ArgumentException ("not enough space");
1077 host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE);
1082 public virtual IEnumerator GetEnumerator ()
1084 return new Hashtable.Enumerator (host, EnumeratorMode.VALUE_MODE);
1090 private class SyncHashtable : Hashtable, IEnumerable {
1092 private Hashtable host;
1094 public SyncHashtable (Hashtable host) {
1096 throw new ArgumentNullException ();
1101 internal SyncHashtable (SerializationInfo info, StreamingContext context)
1103 host = (Hashtable) info.GetValue("ParentTable", typeof(Hashtable));
1106 public override void GetObjectData (SerializationInfo info, StreamingContext context)
1108 info.AddValue ("ParentTable", host);
1113 public override int Count {
1119 public override bool IsSynchronized {
1125 public override Object SyncRoot {
1127 return host.SyncRoot;
1135 public override bool IsFixedSize {
1137 return host.IsFixedSize;
1142 public override bool IsReadOnly {
1144 return host.IsReadOnly;
1148 public override ICollection Keys {
1150 ICollection keys = null;
1151 lock (host.SyncRoot) {
1158 public override ICollection Values {
1160 ICollection vals = null;
1161 lock (host.SyncRoot) {
1170 public override Object this [Object key] {
1175 lock (host.SyncRoot) {
1183 IEnumerator IEnumerable.GetEnumerator ()
1185 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1193 public override void CopyTo (Array array, int arrayIndex)
1195 host.CopyTo (array, arrayIndex);
1201 public override void Add (Object key, Object value)
1203 lock (host.SyncRoot) {
1204 host.Add (key, value);
1208 public override void Clear ()
1210 lock (host.SyncRoot) {
1215 public override bool Contains (Object key)
1217 return (host.Find (key) >= 0);
1220 public override IDictionaryEnumerator GetEnumerator ()
1222 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1225 public override void Remove (Object key)
1227 lock (host.SyncRoot) {
1234 public override bool ContainsKey (object key)
1236 return host.Contains (key);
1239 public override bool ContainsValue (object value)
1241 return host.ContainsValue (value);
1247 public override object Clone ()
1249 lock(host.SyncRoot) {
1250 return new SyncHashtable( (Hashtable) host.Clone () );