2 // System.Collections.Hashtable.cs
5 // Sergey Chaban (serge@wildwestsoftware.com)
9 // Copyright (C) 2004 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 namespace System.Collections {
38 public class Hashtable : IDictionary, ICollection,
39 IEnumerable, ICloneable, ISerializable, IDeserializationCallback
43 internal struct Slot {
46 internal Object value;
48 // Hashcode. Chains are also marked through this.
53 internal class KeyMarker: IObjectReference
55 public readonly static KeyMarker Removed = new KeyMarker();
56 public object GetRealObject (StreamingContext context)
57 { return KeyMarker.Removed; }
64 const int CHAIN_MARKER = ~Int32.MaxValue;
68 private int modificationCount;
69 private float loadFactor;
70 private Slot [] table;
71 private int threshold;
73 private HashKeys hashKeys;
74 private HashValues hashValues;
76 private IHashCodeProvider hcpRef;
77 private IComparer comparerRef;
79 private static readonly int [] primeTbl = {
120 public Hashtable () : this (0, 1.0f) {}
123 public Hashtable (int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
125 throw new ArgumentOutOfRangeException ("capacity", "negative capacity");
127 if (loadFactor < 0.1f || loadFactor > 1.0f || Single.IsNaN (loadFactor))
128 throw new ArgumentOutOfRangeException ("loadFactor", "load factor");
130 if (capacity == 0) ++capacity;
131 this.loadFactor = 0.75f*loadFactor;
132 double tableSize = capacity / this.loadFactor;
134 if (tableSize > Int32.MaxValue)
135 throw new ArgumentException ("Size is too big");
137 int size = (int) tableSize;
138 size = ToPrime (size);
139 this.SetTable (new Slot [size]);
142 this.comparer = comparer;
145 this.modificationCount = 0;
149 public Hashtable (int capacity, float loadFactor) :
150 this (capacity, loadFactor, null, null)
154 public Hashtable (int capacity) : this (capacity, 1.0f)
158 public Hashtable (int capacity,
159 IHashCodeProvider hcp,
161 : this (capacity, 1.0f, hcp, comparer)
166 public Hashtable (IDictionary d, float loadFactor,
167 IHashCodeProvider hcp, IComparer comparer)
168 : this (d!=null ? d.Count : 0,
169 loadFactor, hcp, comparer)
173 throw new ArgumentNullException ("dictionary");
175 IDictionaryEnumerator it = d.GetEnumerator ();
176 while (it.MoveNext ()) {
177 Add (it.Key, it.Value);
182 public Hashtable (IDictionary d, float loadFactor)
183 : this (d, loadFactor, null, null)
188 public Hashtable (IDictionary d) : this (d, 1.0f)
192 public Hashtable (IDictionary d, IHashCodeProvider hcp, IComparer comparer)
193 : this (d, 1.0f, hcp, comparer)
197 public Hashtable (IHashCodeProvider hcp, IComparer comparer)
198 : this (1, 1.0f, hcp, comparer)
202 protected Hashtable (SerializationInfo info, StreamingContext context)
204 loadFactor = (float) info.GetValue ("LoadFactor", typeof(float));
205 modificationCount = (int) info.GetValue ("Version", typeof(int));
206 comparerRef = (IComparer) info.GetValue ("Comparer", typeof (object));
207 hcpRef = (IHashCodeProvider) info.GetValue ("HashCodeProvider", typeof (object));
208 int size = (int) info.GetValue ("HashSize", typeof(int));
209 Object [] keys = (Object []) info.GetValue("Keys", typeof(Object [] ));
210 Object [] values = (Object []) info.GetValue("Values", typeof(Object [] ));
212 if (keys.Length != values.Length)
213 throw new SerializationException("Keys and values of uneven size");
215 size = ToPrime (size);
216 this.SetTable (new Slot [size]);
218 for(int i=0;i<keys.Length;i++) {
219 Add(keys[i], values[i]);
230 protected IComparer comparer {
239 protected IHashCodeProvider hcp {
250 public virtual int Count {
256 public virtual bool IsSynchronized {
262 public virtual Object SyncRoot {
272 public virtual bool IsFixedSize {
279 public virtual bool IsReadOnly {
285 public virtual ICollection Keys {
287 if (this.hashKeys == null)
288 this.hashKeys = new HashKeys (this);
289 return this.hashKeys;
293 public virtual ICollection Values {
295 if (this.hashValues == null)
296 this.hashValues = new HashValues (this);
297 return this.hashValues;
303 public virtual Object this [Object key] {
305 return GetImpl (key);
308 PutImpl (key, value, true);
322 IEnumerator IEnumerable.GetEnumerator ()
324 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
329 public virtual void CopyTo (Array array, int arrayIndex)
332 throw new ArgumentNullException ("array");
335 throw new ArgumentOutOfRangeException ("arrayIndex");
338 throw new ArgumentException ("array is multidimensional");
340 if ((array.Length > 0) && (arrayIndex >= array.Length))
341 throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
343 if (arrayIndex + this.inUse > array.Length)
344 throw new ArgumentException ("Not enough room from arrayIndex to end of array for this Hashtable");
346 IDictionaryEnumerator it = GetEnumerator ();
349 while (it.MoveNext ()) {
350 array.SetValue (it.Entry, i++);
357 public virtual void Add (Object key, Object value)
359 PutImpl (key, value, false);
362 public virtual void Clear ()
364 for (int i = 0;i<table.Length;i++) {
365 table [i].key = null;
366 table [i].value = null;
367 table [i].hashMix = 0;
374 public virtual bool Contains (Object key)
376 return (Find (key) >= 0);
379 public virtual IDictionaryEnumerator GetEnumerator ()
381 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
384 public virtual void Remove (Object key)
388 Slot [] table = this.table;
389 int h = table [i].hashMix;
391 table [i].hashMix = h;
392 table [i].key = (h != 0)
395 table [i].value = null;
401 public virtual bool ContainsKey (object key)
403 return Contains (key);
406 public virtual bool ContainsValue (object value)
408 int size = this.table.Length;
409 Slot [] table = this.table;
411 for (int i = 0; i < size; i++) {
412 Slot entry = table [i];
413 if (entry.key != null && entry.key!= KeyMarker.Removed
414 && entry.value == null) {
419 for (int i = 0; i < size; i++) {
420 Slot entry = table [i];
421 if (entry.key != null && entry.key!= KeyMarker.Removed
422 && value.Equals (entry.value)) {
433 public virtual object Clone ()
435 Hashtable ht = new Hashtable (Count, hcp, comparer);
437 ht.loadFactor = this.loadFactor;
439 // FIXME: maybe it's faster to simply
440 // copy the back-end array?
442 IDictionaryEnumerator it = GetEnumerator ();
443 while (it.MoveNext ()) {
444 ht [it.Key] = it.Value;
450 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
453 throw new ArgumentNullException ("info");
455 info.AddValue ("LoadFactor", loadFactor);
456 info.AddValue ("Version", modificationCount);
457 info.AddValue ("Comparer", comparerRef);
458 info.AddValue ("HashCodeProvider", hcpRef);
459 info.AddValue ("HashSize", this.table.Length);
461 Object [] keys = new Object[inUse];
462 CopyToArray(keys, 0, EnumeratorMode.KEY_MODE);
465 Object [] values = new Object[inUse];
466 CopyToArray(values, 0, EnumeratorMode.VALUE_MODE);
468 info.AddValue ("Keys", keys);
469 info.AddValue ("Values", values);
473 public virtual void OnDeserialization (object sender)
478 /// Returns a synchronized (thread-safe)
479 /// wrapper for the Hashtable.
481 public static Hashtable Synchronized (Hashtable table)
484 throw new ArgumentNullException("table");
485 return new SyncHashtable (table);
491 // Protected instance methods
494 /// <summary>Returns the hash code for the specified key.</summary>
495 protected virtual int GetHash (Object key)
497 IHashCodeProvider hcp = this.hcp;
499 ? hcp.GetHashCode (key)
500 : key.GetHashCode ();
504 /// Compares a specific Object with a specific key
505 /// in the Hashtable.
507 protected virtual bool KeyEquals (Object item, Object key)
509 IComparer c = this.comparer;
511 return (c.Compare (item, key) == 0);
513 return item.Equals (key);
519 // Private instance methods
522 private void AdjustThreshold ()
524 int size = table.Length;
526 threshold = (int) (size*loadFactor);
527 if (this.threshold >= size)
531 private void SetTable (Slot [] table)
534 throw new ArgumentNullException ("table");
540 private Object GetImpl (Object key)
545 return table [i].value;
551 private int Find (Object key)
554 throw new ArgumentNullException ("key", "null key");
556 uint size = (uint) this.table.Length;
557 int h = this.GetHash (key) & Int32.MaxValue;
559 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
560 Slot[] table = this.table;
562 for (int i = 0; i < size;i++) {
563 int indx = (int) (spot % size);
564 Slot entry = table [indx];
565 Object k = entry.key;
568 if ((entry.hashMix & Int32.MaxValue) == h
569 && this.KeyEquals (key, k)) {
573 if ((entry.hashMix & CHAIN_MARKER) == 0)
582 private void Rehash ()
584 int oldSize = this.table.Length;
586 // From the SDK docs:
587 // Hashtable is automatically increased
588 // to the smallest prime number that is larger
589 // than twice the current number of Hashtable buckets
590 uint newSize = (uint)ToPrime ((oldSize<<1)|1);
593 Slot [] newTable = new Slot [newSize];
594 Slot [] table = this.table;
596 for (int i = 0;i<oldSize;i++) {
599 int h = s.hashMix & Int32.MaxValue;
601 uint step = ((uint) (h>>5)+1)% (newSize-1)+1;
602 for (uint j = spot%newSize;;spot+= step, j = spot%newSize) {
603 // No check for KeyMarker.Removed here,
604 // because the table is just allocated.
605 if (newTable [j].key == null) {
606 newTable [j].key = s.key;
607 newTable [j].value = s.value;
608 newTable [j].hashMix |= h;
611 newTable [j].hashMix |= CHAIN_MARKER;
617 ++this.modificationCount;
619 this.SetTable (newTable);
623 private void PutImpl (Object key, Object value, bool overwrite)
626 throw new ArgumentNullException ("key", "null key");
628 uint size = (uint)this.table.Length;
629 if (this.inUse >= this.threshold) {
631 size = (uint)this.table.Length;
634 int h = this.GetHash (key) & Int32.MaxValue;
636 uint step = (uint) ((spot>>5)+1)% (size-1)+1;
637 Slot [] table = this.table;
641 for (int i = 0; i < size; i++) {
642 int indx = (int) (spot % size);
643 entry = table [indx];
646 && entry.key == KeyMarker.Removed
647 && (entry.hashMix & CHAIN_MARKER) != 0)
650 if (entry.key == null ||
651 (entry.key == KeyMarker.Removed
652 && (entry.hashMix & CHAIN_MARKER) == 0)) {
659 if ((entry.hashMix & Int32.MaxValue) == h && KeyEquals (key, entry.key)) {
661 table [indx].value = value;
662 ++this.modificationCount;
665 // An entry with the same key already exists in the Hashtable.
666 throw new ArgumentException (
667 "Key duplication when adding: " + key);
672 if (freeIndx == -1) {
673 table [indx].hashMix |= CHAIN_MARKER;
681 table [freeIndx].key = key;
682 table [freeIndx].value = value;
683 table [freeIndx].hashMix |= h;
686 ++this.modificationCount;
691 private void CopyToArray (Array arr, int i,
694 IEnumerator it = new Enumerator (this, mode);
696 while (it.MoveNext ()) {
697 arr.SetValue (it.Current, i++);
704 // Private static methods
706 private static bool TestPrime (int x)
709 for (int n = 3; n< (int)Math.Sqrt (x); n += 2) {
715 // There is only one even prime - 2.
719 private static int CalcPrime (int x)
721 for (int i = (x & (~1))-1; i< Int32.MaxValue; i += 2) {
722 if (TestPrime (i)) return i;
727 private static int ToPrime (int x)
729 for (int i = 0; i < primeTbl.Length; i++) {
730 if (x <= primeTbl [i])
733 return CalcPrime (x);
743 private enum EnumeratorMode : int {KEY_MODE = 0, VALUE_MODE, ENTRY_MODE};
745 private sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
747 private Hashtable host;
751 private EnumeratorMode mode;
753 private Object currentKey;
754 private Object currentValue;
756 private readonly static string xstr = "Hashtable.Enumerator: snapshot out of sync.";
758 public Enumerator (Hashtable host, EnumeratorMode mode) {
760 stamp = host.modificationCount;
761 size = host.table.Length;
766 public Enumerator (Hashtable host)
767 : this (host, EnumeratorMode.KEY_MODE) {}
770 private void FailFast ()
772 if (host.modificationCount != stamp) {
773 throw new InvalidOperationException (xstr);
786 public bool MoveNext ()
791 while (++pos < size) {
792 Slot entry = host.table [pos];
794 if (entry.key != null && entry.key != KeyMarker.Removed) {
795 currentKey = entry.key;
796 currentValue = entry.value;
807 public DictionaryEntry Entry
810 if (currentKey == null) throw new InvalidOperationException ();
812 return new DictionaryEntry (currentKey, currentValue);
818 if (currentKey == null) throw new InvalidOperationException ();
824 public Object Value {
826 if (currentKey == null) throw new InvalidOperationException ();
832 public Object Current {
834 if (currentKey == null) throw new InvalidOperationException ();
837 case EnumeratorMode.KEY_MODE:
839 case EnumeratorMode.VALUE_MODE:
841 case EnumeratorMode.ENTRY_MODE:
842 return new DictionaryEntry (currentKey, currentValue);
844 throw new Exception ("should never happen");
851 private class HashKeys : ICollection, IEnumerable {
853 private Hashtable host;
855 public HashKeys (Hashtable host) {
857 throw new ArgumentNullException ();
864 public virtual int Count {
870 public virtual bool IsSynchronized {
872 return host.IsSynchronized;
876 public virtual Object SyncRoot {
877 get {return host.SyncRoot;}
880 public virtual void CopyTo (Array array, int arrayIndex)
883 throw new ArgumentNullException ("array");
885 throw new ArgumentException ("array");
887 throw new ArgumentOutOfRangeException ("arrayIndex");
888 if (array.Length - arrayIndex < Count)
889 throw new ArgumentException ("not enough space");
891 host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE);
896 public virtual IEnumerator GetEnumerator ()
898 return new Hashtable.Enumerator (host, EnumeratorMode.KEY_MODE);
903 private class HashValues : ICollection, IEnumerable {
905 private Hashtable host;
907 public HashValues (Hashtable host) {
909 throw new ArgumentNullException ();
916 public virtual int Count {
922 public virtual bool IsSynchronized {
924 return host.IsSynchronized;
928 public virtual Object SyncRoot {
930 return host.SyncRoot;
934 public virtual void CopyTo (Array array, int arrayIndex)
937 throw new ArgumentNullException ("array");
939 throw new ArgumentException ("array");
941 throw new ArgumentOutOfRangeException ("arrayIndex");
942 if (array.Length - arrayIndex < Count)
943 throw new ArgumentException ("not enough space");
945 host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE);
950 public virtual IEnumerator GetEnumerator ()
952 return new Hashtable.Enumerator (host, EnumeratorMode.VALUE_MODE);
958 private class SyncHashtable : Hashtable, IEnumerable {
960 private Hashtable host;
962 public SyncHashtable (Hashtable host) {
964 throw new ArgumentNullException ();
969 internal SyncHashtable (SerializationInfo info, StreamingContext context)
971 host = (Hashtable) info.GetValue("ParentTable", typeof(Hashtable));
974 public override void GetObjectData (SerializationInfo info, StreamingContext context)
976 info.AddValue ("ParentTable", host);
981 public override int Count {
987 public override bool IsSynchronized {
993 public override Object SyncRoot {
995 return host.SyncRoot;
1003 public override bool IsFixedSize {
1005 return host.IsFixedSize;
1010 public override bool IsReadOnly {
1012 return host.IsReadOnly;
1016 public override ICollection Keys {
1018 ICollection keys = null;
1019 lock (host.SyncRoot) {
1026 public override ICollection Values {
1028 ICollection vals = null;
1029 lock (host.SyncRoot) {
1038 public override Object this [Object key] {
1040 return host.GetImpl (key);
1043 lock (host.SyncRoot) {
1044 host.PutImpl (key, value, true);
1051 IEnumerator IEnumerable.GetEnumerator ()
1053 return new Enumerator (host, EnumeratorMode.KEY_MODE);
1061 public override void CopyTo (Array array, int arrayIndex)
1063 host.CopyTo (array, arrayIndex);
1069 public override void Add (Object key, Object value)
1071 lock (host.SyncRoot) {
1072 host.PutImpl (key, value, false);
1076 public override void Clear ()
1078 lock (host.SyncRoot) {
1083 public override bool Contains (Object key)
1085 return (host.Find (key) >= 0);
1088 public override IDictionaryEnumerator GetEnumerator ()
1090 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1093 public override void Remove (Object key)
1095 lock (host.SyncRoot) {
1102 public override bool ContainsKey (object key)
1104 return host.Contains (key);
1107 public override bool ContainsValue (object value)
1109 return host.ContainsValue (value);
1115 public override object Clone ()
1117 lock(host.SyncRoot) {
1118 return new SyncHashtable( (Hashtable) host.Clone () );