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;
35 using System.Runtime.ConstrainedExecution;
36 using System.Runtime.InteropServices;
37 using System.Diagnostics;
39 namespace System.Collections {
44 [DebuggerDisplay ("Count={Count}")]
45 [DebuggerTypeProxy (typeof (CollectionDebuggerView))]
46 public class Hashtable : IDictionary, ICollection,
47 IEnumerable, ICloneable, ISerializable, IDeserializationCallback {
49 internal class Hashtable : IDictionary, ICollection, IEnumerable {
53 internal struct Slot {
56 internal Object value;
60 internal class KeyMarker
65 public readonly static KeyMarker Removed = new KeyMarker();
67 public object GetRealObject (StreamingContext context)
69 return KeyMarker.Removed;
78 const int CHAIN_MARKER = ~Int32.MaxValue;
81 private Slot [] table;
82 // Hashcodes of the corresponding entries in the slot table. Kept separate to
84 private int [] hashes;
85 private HashKeys hashKeys;
86 private HashValues hashValues;
87 private IHashCodeProvider hcpRef;
88 private IComparer comparerRef;
89 private SerializationInfo serializationInfo;
90 private IEqualityComparer equalityComparer;
93 private int modificationCount;
94 private float loadFactor;
95 private int threshold;
97 private static readonly int [] primeTbl = {
138 public Hashtable () : this (0, 1.0f) {}
140 [Obsolete ("Please use Hashtable(int, float, IEqualityComparer) instead")]
141 public Hashtable (int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
143 throw new ArgumentOutOfRangeException ("capacity", "negative capacity");
145 if (loadFactor < 0.1f || loadFactor > 1.0f || Single.IsNaN (loadFactor))
146 throw new ArgumentOutOfRangeException ("loadFactor", "load factor");
148 if (capacity == 0) ++capacity;
149 this.loadFactor = 0.75f*loadFactor;
150 double tableSize = capacity / this.loadFactor;
152 if (tableSize > Int32.MaxValue)
153 throw new ArgumentException ("Size is too big");
155 int size = (int) tableSize;
156 size = ToPrime (size);
157 this.SetTable (new Slot [size], new int [size]);
160 this.comparer = comparer;
163 this.modificationCount = 0;
166 public Hashtable (int capacity, float loadFactor) :
167 this (capacity, loadFactor, null, null)
171 public Hashtable (int capacity) : this (capacity, 1.0f)
176 // Used as a faster Clone
178 internal Hashtable (Hashtable source)
180 inUse = source.inUse;
181 loadFactor = source.loadFactor;
183 table = (Slot []) source.table.Clone ();
184 hashes = (int []) source.hashes.Clone ();
185 threshold = source.threshold;
186 hcpRef = source.hcpRef;
187 comparerRef = source.comparerRef;
188 equalityComparer = source.equalityComparer;
191 [Obsolete ("Please use Hashtable(int, IEqualityComparer) instead")]
192 public Hashtable (int capacity,
193 IHashCodeProvider hcp,
195 : this (capacity, 1.0f, hcp, comparer)
199 [Obsolete ("Please use Hashtable(IDictionary, float, IEqualityComparer) instead")]
200 public Hashtable (IDictionary d, float loadFactor,
201 IHashCodeProvider hcp, IComparer comparer)
202 : this (d!=null ? d.Count : 0,
203 loadFactor, hcp, comparer)
207 throw new ArgumentNullException ("dictionary");
209 IDictionaryEnumerator it = d.GetEnumerator ();
210 while (it.MoveNext ()) {
211 Add (it.Key, it.Value);
216 public Hashtable (IDictionary d, float loadFactor)
217 : this (d, loadFactor, null, null)
222 public Hashtable (IDictionary d) : this (d, 1.0f)
226 [Obsolete ("Please use Hashtable(IDictionary, IEqualityComparer) instead")]
227 public Hashtable (IDictionary d, IHashCodeProvider hcp, IComparer comparer)
228 : this (d, 1.0f, hcp, comparer)
232 [Obsolete ("Please use Hashtable(IEqualityComparer) instead")]
233 public Hashtable (IHashCodeProvider hcp, IComparer comparer)
234 : this (1, 1.0f, hcp, comparer)
238 protected Hashtable (SerializationInfo info, StreamingContext context)
240 serializationInfo = info;
243 public Hashtable (IDictionary d, IEqualityComparer equalityComparer) : this (d, 1.0f, equalityComparer)
247 public Hashtable (IDictionary d, float loadFactor, IEqualityComparer equalityComparer) : this (d, loadFactor)
249 this.equalityComparer = equalityComparer;
252 public Hashtable (IEqualityComparer equalityComparer) : this (1, 1.0f, equalityComparer)
256 public Hashtable (int capacity, IEqualityComparer equalityComparer) : this (capacity, 1.0f, equalityComparer)
260 public Hashtable (int capacity, float loadFactor, IEqualityComparer equalityComparer) : this (capacity, loadFactor)
262 this.equalityComparer = equalityComparer;
269 [Obsolete ("Please use EqualityComparer property.")]
270 protected IComparer comparer {
279 [Obsolete ("Please use EqualityComparer property.")]
280 protected IHashCodeProvider hcp {
289 protected IEqualityComparer EqualityComparer {
291 return equalityComparer;
297 public virtual int Count {
303 public virtual bool IsSynchronized {
309 public virtual Object SyncRoot {
319 public virtual bool IsFixedSize {
326 public virtual bool IsReadOnly {
332 public virtual ICollection Keys {
334 if (this.hashKeys == null)
335 this.hashKeys = new HashKeys (this);
336 return this.hashKeys;
340 public virtual ICollection Values {
342 if (this.hashValues == null)
343 this.hashValues = new HashValues (this);
344 return this.hashValues;
350 public virtual Object this [Object key] {
353 throw new ArgumentNullException ("key", "null key");
355 Slot [] table = this.table;
356 int [] hashes = this.hashes;
357 uint size = (uint) table.Length;
358 int h = this.GetHash (key) & Int32.MaxValue;
360 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
363 for (uint i = size; i > 0; i--) {
365 Slot entry = table [indx];
366 int hashMix = hashes [indx];
367 Object k = entry.key;
371 if (k == key || ((hashMix & Int32.MaxValue) == h
372 && this.KeyEquals (key, k))) {
376 if ((hashMix & CHAIN_MARKER) == 0)
386 PutImpl (key, value, true);
400 IEnumerator IEnumerable.GetEnumerator ()
402 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
407 public virtual void CopyTo (Array array, int arrayIndex)
410 throw new ArgumentNullException ("array");
413 throw new ArgumentOutOfRangeException ("arrayIndex");
416 throw new ArgumentException ("array is multidimensional");
418 if ((array.Length > 0) && (arrayIndex >= array.Length))
419 throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
421 if (arrayIndex + this.inUse > array.Length)
422 throw new ArgumentException ("Not enough room from arrayIndex to end of array for this Hashtable");
424 IDictionaryEnumerator it = GetEnumerator ();
427 while (it.MoveNext ()) {
428 array.SetValue (it.Entry, i++);
435 public virtual void Add (Object key, Object value)
437 PutImpl (key, value, false);
440 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.Success)]
441 public virtual void Clear ()
443 for (int i = 0;i<table.Length;i++) {
444 table [i].key = null;
445 table [i].value = null;
453 public virtual bool Contains (Object key)
455 return (Find (key) >= 0);
458 public virtual IDictionaryEnumerator GetEnumerator ()
460 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
463 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)]
464 public virtual void Remove (Object key)
468 Slot [] table = this.table;
472 table [i].key = (h != 0)
475 table [i].value = null;
481 public virtual bool ContainsKey (object key)
483 return Contains (key);
486 public virtual bool ContainsValue (object value)
488 int size = this.table.Length;
489 Slot [] table = this.table;
491 for (int i = 0; i < size; i++) {
492 Slot entry = table [i];
493 if (entry.key != null && entry.key!= KeyMarker.Removed
494 && entry.value == null) {
499 for (int i = 0; i < size; i++) {
500 Slot entry = table [i];
501 if (entry.key != null && entry.key!= KeyMarker.Removed
502 && value.Equals (entry.value)) {
513 public virtual object Clone ()
515 return new Hashtable (this);
518 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
521 throw new ArgumentNullException ("info");
523 info.AddValue ("LoadFactor", loadFactor);
524 info.AddValue ("Version", modificationCount);
525 if (equalityComparer != null)
526 info.AddValue ("KeyComparer", equalityComparer);
528 info.AddValue ("Comparer", comparerRef);
530 info.AddValue ("HashCodeProvider", hcpRef);
531 info.AddValue ("HashSize", this.table.Length);
533 Object [] keys = new Object[inUse];
534 CopyToArray(keys, 0, EnumeratorMode.KEY_MODE);
537 Object [] values = new Object[inUse];
538 CopyToArray(values, 0, EnumeratorMode.VALUE_MODE);
540 info.AddValue ("Keys", keys);
541 info.AddValue ("Values", values);
543 info.AddValue ("equalityComparer", equalityComparer);
546 [MonoTODO ("Serialize equalityComparer")]
547 public virtual void OnDeserialization (object sender)
549 if (serializationInfo == null) return;
551 loadFactor = (float) serializationInfo.GetValue ("LoadFactor", typeof(float));
552 modificationCount = (int) serializationInfo.GetValue ("Version", typeof(int));
554 equalityComparer = (IEqualityComparer) serializationInfo.GetValue ("KeyComparer", typeof (object));
556 // If not found, try to get "Comparer"
559 if (equalityComparer == null)
560 comparerRef = (IComparer) serializationInfo.GetValue ("Comparer", typeof (object));
562 hcpRef = (IHashCodeProvider) serializationInfo.GetValue ("HashCodeProvider", typeof (object));
563 } catch {} // Missing value => null
564 int size = (int) serializationInfo.GetValue ("HashSize", typeof(int));
566 Object [] keys = (Object []) serializationInfo.GetValue("Keys", typeof(Object [] ));
567 Object [] values = (Object []) serializationInfo.GetValue("Values", typeof(Object [] ));
569 if (keys.Length != values.Length)
570 throw new SerializationException("Keys and values of uneven size");
572 size = ToPrime (size);
573 this.SetTable (new Slot [size], new int [size]);
575 for(int i=0;i<keys.Length;i++)
576 Add(keys[i], values[i]);
580 serializationInfo = null;
584 /// Returns a synchronized (thread-safe)
585 /// wrapper for the Hashtable.
587 public static Hashtable Synchronized (Hashtable table)
590 throw new ArgumentNullException("table");
591 return new SyncHashtable (table);
597 // Protected instance methods
600 /// <summary>Returns the hash code for the specified key.</summary>
601 protected virtual int GetHash (Object key)
603 if (equalityComparer != null)
604 return equalityComparer.GetHashCode (key);
606 return key.GetHashCode ();
608 return hcpRef.GetHashCode (key);
612 /// Compares a specific Object with a specific key
613 /// in the Hashtable.
615 protected virtual bool KeyEquals (Object item, Object key)
617 if (key == KeyMarker.Removed)
619 if (equalityComparer != null)
620 return equalityComparer.Equals (item, key);
621 if (comparerRef == null)
622 return item.Equals (key);
624 return comparerRef.Compare (item, key) == 0;
630 // Private instance methods
633 private void AdjustThreshold ()
635 int size = table.Length;
637 threshold = (int) (size*loadFactor);
638 if (this.threshold >= size)
642 private void SetTable (Slot [] table, int[] hashes)
645 throw new ArgumentNullException ("table");
648 this.hashes = hashes;
652 private int Find (Object key)
655 throw new ArgumentNullException ("key", "null key");
657 Slot [] table = this.table;
658 int [] hashes = this.hashes;
659 uint size = (uint) table.Length;
660 int h = this.GetHash (key) & Int32.MaxValue;
662 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
665 for (uint i = size; i > 0; i--) {
667 Slot entry = table [indx];
668 int hashMix = hashes [indx];
669 Object k = entry.key;
673 if (k == key || ((hashMix & Int32.MaxValue) == h
674 && this.KeyEquals (key, k))) {
678 if ((hashMix & CHAIN_MARKER) == 0)
687 private void Rehash ()
689 int oldSize = this.table.Length;
691 // From the SDK docs:
692 // Hashtable is automatically increased
693 // to the smallest prime number that is larger
694 // than twice the current number of Hashtable buckets
695 uint newSize = (uint)ToPrime ((oldSize<<1)|1);
698 Slot [] newTable = new Slot [newSize];
699 Slot [] table = this.table;
700 int [] newHashes = new int [newSize];
701 int [] hashes = this.hashes;
703 for (int i = 0;i<oldSize;i++) {
706 int h = hashes [i] & Int32.MaxValue;
708 uint step = ((uint) (h>>5)+1)% (newSize-1)+1;
709 for (uint j = spot%newSize;;spot+= step, j = spot%newSize) {
710 // No check for KeyMarker.Removed here,
711 // because the table is just allocated.
712 if (newTable [j].key == null) {
713 newTable [j].key = s.key;
714 newTable [j].value = s.value;
718 newHashes [j] |= CHAIN_MARKER;
724 ++this.modificationCount;
726 this.SetTable (newTable, newHashes);
730 private void PutImpl (Object key, Object value, bool overwrite)
733 throw new ArgumentNullException ("key", "null key");
735 if (this.inUse >= this.threshold)
738 uint size = (uint)this.table.Length;
740 int h = this.GetHash (key) & Int32.MaxValue;
742 uint step = (uint) ((spot>>5)+1)% (size-1)+1;
743 Slot [] table = this.table;
744 int [] hashes = this.hashes;
748 for (int i = 0; i < size; i++) {
749 int indx = (int) (spot % size);
750 entry = table [indx];
751 int hashMix = hashes [indx];
754 && entry.key == KeyMarker.Removed
755 && (hashMix & CHAIN_MARKER) != 0)
758 if (entry.key == null ||
759 (entry.key == KeyMarker.Removed
760 && (hashMix & CHAIN_MARKER) == 0)) {
767 if ((hashMix & Int32.MaxValue) == h && KeyEquals (key, entry.key)) {
769 table [indx].value = value;
770 ++this.modificationCount;
773 // An entry with the same key already exists in the Hashtable.
774 throw new ArgumentException (
775 "Key duplication when adding: " + key);
780 if (freeIndx == -1) {
781 hashes [indx] |= CHAIN_MARKER;
789 table [freeIndx].key = key;
790 table [freeIndx].value = value;
791 hashes [freeIndx] |= h;
794 ++this.modificationCount;
799 private void CopyToArray (Array arr, int i,
802 IEnumerator it = new Enumerator (this, mode);
804 while (it.MoveNext ()) {
805 arr.SetValue (it.Current, i++);
812 // Private static methods
814 internal static bool TestPrime (int x)
817 int top = (int)Math.Sqrt (x);
819 for (int n = 3; n < top; n += 2) {
825 // There is only one even prime - 2.
829 internal static int CalcPrime (int x)
831 for (int i = (x & (~1))-1; i< Int32.MaxValue; i += 2) {
832 if (TestPrime (i)) return i;
837 internal static int ToPrime (int x)
839 for (int i = 0; i < primeTbl.Length; i++) {
840 if (x <= primeTbl [i])
843 return CalcPrime (x);
853 private enum EnumeratorMode : int {KEY_MODE = 0, VALUE_MODE, ENTRY_MODE};
856 private sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
858 private Hashtable host;
859 private Object currentKey;
860 private Object currentValue;
865 private EnumeratorMode mode;
867 const string xstr = "Hashtable.Enumerator: snapshot out of sync.";
869 public Enumerator (Hashtable host, EnumeratorMode mode) {
871 stamp = host.modificationCount;
872 size = host.table.Length;
877 private void FailFast ()
879 if (host.modificationCount != stamp) {
880 throw new InvalidOperationException (xstr);
893 public bool MoveNext ()
898 while (++pos < size) {
899 Slot entry = host.table [pos];
901 if (entry.key != null && entry.key != KeyMarker.Removed) {
902 currentKey = entry.key;
903 currentValue = entry.value;
914 public DictionaryEntry Entry
917 if (currentKey == null) throw new InvalidOperationException ();
919 return new DictionaryEntry (currentKey, currentValue);
925 if (currentKey == null) throw new InvalidOperationException ();
931 public Object Value {
933 if (currentKey == null) throw new InvalidOperationException ();
939 public Object Current {
941 if (currentKey == null) throw new InvalidOperationException ();
944 case EnumeratorMode.KEY_MODE:
946 case EnumeratorMode.VALUE_MODE:
948 case EnumeratorMode.ENTRY_MODE:
949 return new DictionaryEntry (currentKey, currentValue);
951 throw new Exception ("should never happen");
958 [DebuggerDisplay ("Count={Count}")]
959 [DebuggerTypeProxy (typeof (CollectionDebuggerView))]
961 private class HashKeys : ICollection, IEnumerable {
963 private Hashtable host;
965 public HashKeys (Hashtable host) {
967 throw new ArgumentNullException ();
974 public virtual int Count {
980 public virtual bool IsSynchronized {
982 return host.IsSynchronized;
986 public virtual Object SyncRoot {
987 get {return host.SyncRoot;}
990 public virtual void CopyTo (Array array, int arrayIndex)
993 throw new ArgumentNullException ("array");
995 throw new ArgumentException ("array");
997 throw new ArgumentOutOfRangeException ("arrayIndex");
998 if (array.Length - arrayIndex < Count)
999 throw new ArgumentException ("not enough space");
1001 host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE);
1006 public virtual IEnumerator GetEnumerator ()
1008 return new Hashtable.Enumerator (host, EnumeratorMode.KEY_MODE);
1014 [DebuggerDisplay ("Count={Count}")]
1015 [DebuggerTypeProxy (typeof (CollectionDebuggerView))]
1017 private class HashValues : ICollection, IEnumerable {
1019 private Hashtable host;
1021 public HashValues (Hashtable host) {
1023 throw new ArgumentNullException ();
1030 public virtual int Count {
1036 public virtual bool IsSynchronized {
1038 return host.IsSynchronized;
1042 public virtual Object SyncRoot {
1044 return host.SyncRoot;
1048 public virtual void CopyTo (Array array, int arrayIndex)
1051 throw new ArgumentNullException ("array");
1052 if (array.Rank != 1)
1053 throw new ArgumentException ("array");
1055 throw new ArgumentOutOfRangeException ("arrayIndex");
1056 if (array.Length - arrayIndex < Count)
1057 throw new ArgumentException ("not enough space");
1059 host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE);
1064 public virtual IEnumerator GetEnumerator ()
1066 return new Hashtable.Enumerator (host, EnumeratorMode.VALUE_MODE);
1072 private class SyncHashtable : Hashtable, IEnumerable {
1074 private Hashtable host;
1076 public SyncHashtable (Hashtable host) {
1078 throw new ArgumentNullException ();
1083 internal SyncHashtable (SerializationInfo info, StreamingContext context)
1085 host = (Hashtable) info.GetValue("ParentTable", typeof(Hashtable));
1088 public override void GetObjectData (SerializationInfo info, StreamingContext context)
1090 info.AddValue ("ParentTable", host);
1095 public override int Count {
1101 public override bool IsSynchronized {
1107 public override Object SyncRoot {
1109 return host.SyncRoot;
1117 public override bool IsFixedSize {
1119 return host.IsFixedSize;
1124 public override bool IsReadOnly {
1126 return host.IsReadOnly;
1130 public override ICollection Keys {
1132 ICollection keys = null;
1133 lock (host.SyncRoot) {
1140 public override ICollection Values {
1142 ICollection vals = null;
1143 lock (host.SyncRoot) {
1152 public override Object this [Object key] {
1157 lock (host.SyncRoot) {
1165 IEnumerator IEnumerable.GetEnumerator ()
1167 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1175 public override void CopyTo (Array array, int arrayIndex)
1177 host.CopyTo (array, arrayIndex);
1183 public override void Add (Object key, Object value)
1185 lock (host.SyncRoot) {
1186 host.Add (key, value);
1190 public override void Clear ()
1192 lock (host.SyncRoot) {
1197 public override bool Contains (Object key)
1199 return (host.Find (key) >= 0);
1202 public override IDictionaryEnumerator GetEnumerator ()
1204 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1207 public override void Remove (Object key)
1209 lock (host.SyncRoot) {
1216 public override bool ContainsKey (object key)
1218 return host.Contains (key);
1221 public override bool ContainsValue (object value)
1223 return host.ContainsValue (value);
1229 public override object Clone ()
1231 lock(host.SyncRoot) {
1232 return new SyncHashtable( (Hashtable) host.Clone () );