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;
36 using System.Runtime.ConstrainedExecution;
39 namespace System.Collections {
42 public class Hashtable : IDictionary, ICollection,
43 IEnumerable, ICloneable, ISerializable, IDeserializationCallback
47 internal struct Slot {
50 internal Object value;
52 // Hashcode. Chains are also marked through this.
57 internal class KeyMarker: IObjectReference
59 public readonly static KeyMarker Removed = new KeyMarker();
60 public object GetRealObject (StreamingContext context)
61 { return KeyMarker.Removed; }
68 const int CHAIN_MARKER = ~Int32.MaxValue;
72 private int modificationCount;
73 private float loadFactor;
74 private Slot [] table;
75 private int threshold;
77 private HashKeys hashKeys;
78 private HashValues hashValues;
80 private IHashCodeProvider hcpRef;
81 private IComparer comparerRef;
82 private SerializationInfo serializationInfo;
84 private static readonly int [] primeTbl = {
125 public Hashtable () : this (0, 1.0f) {}
128 public Hashtable (int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
130 throw new ArgumentOutOfRangeException ("capacity", "negative capacity");
132 if (loadFactor < 0.1f || loadFactor > 1.0f || Single.IsNaN (loadFactor))
133 throw new ArgumentOutOfRangeException ("loadFactor", "load factor");
135 if (capacity == 0) ++capacity;
136 this.loadFactor = 0.75f*loadFactor;
137 double tableSize = capacity / this.loadFactor;
139 if (tableSize > Int32.MaxValue)
140 throw new ArgumentException ("Size is too big");
142 int size = (int) tableSize;
143 size = ToPrime (size);
144 this.SetTable (new Slot [size]);
147 this.comparer = comparer;
150 this.modificationCount = 0;
154 public Hashtable (int capacity, float loadFactor) :
155 this (capacity, loadFactor, null, null)
159 public Hashtable (int capacity) : this (capacity, 1.0f)
163 public Hashtable (int capacity,
164 IHashCodeProvider hcp,
166 : this (capacity, 1.0f, hcp, comparer)
171 public Hashtable (IDictionary d, float loadFactor,
172 IHashCodeProvider hcp, IComparer comparer)
173 : this (d!=null ? d.Count : 0,
174 loadFactor, hcp, comparer)
178 throw new ArgumentNullException ("dictionary");
180 IDictionaryEnumerator it = d.GetEnumerator ();
181 while (it.MoveNext ()) {
182 Add (it.Key, it.Value);
187 public Hashtable (IDictionary d, float loadFactor)
188 : this (d, loadFactor, null, null)
193 public Hashtable (IDictionary d) : this (d, 1.0f)
197 public Hashtable (IDictionary d, IHashCodeProvider hcp, IComparer comparer)
198 : this (d, 1.0f, hcp, comparer)
202 public Hashtable (IHashCodeProvider hcp, IComparer comparer)
203 : this (1, 1.0f, hcp, comparer)
207 protected Hashtable (SerializationInfo info, StreamingContext context)
209 serializationInfo = info;
216 protected IComparer comparer {
225 protected IHashCodeProvider hcp {
236 public virtual int Count {
242 public virtual bool IsSynchronized {
248 public virtual Object SyncRoot {
258 public virtual bool IsFixedSize {
265 public virtual bool IsReadOnly {
271 public virtual ICollection Keys {
273 if (this.hashKeys == null)
274 this.hashKeys = new HashKeys (this);
275 return this.hashKeys;
279 public virtual ICollection Values {
281 if (this.hashValues == null)
282 this.hashValues = new HashValues (this);
283 return this.hashValues;
289 public virtual Object this [Object key] {
292 throw new ArgumentNullException ("key", "null key");
294 Slot [] table = this.table;
295 uint size = (uint) table.Length;
296 int h = this.GetHash (key) & Int32.MaxValue;
298 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
301 for (uint i = size; i > 0; i--) {
303 Slot entry = table [indx];
304 Object k = entry.key;
308 if (k == key || ((entry.hashMix & Int32.MaxValue) == h
309 && this.KeyEquals (key, k))) {
313 if ((entry.hashMix & CHAIN_MARKER) == 0)
323 PutImpl (key, value, true);
337 IEnumerator IEnumerable.GetEnumerator ()
339 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
344 public virtual void CopyTo (Array array, int arrayIndex)
347 throw new ArgumentNullException ("array");
350 throw new ArgumentOutOfRangeException ("arrayIndex");
353 throw new ArgumentException ("array is multidimensional");
355 if ((array.Length > 0) && (arrayIndex >= array.Length))
356 throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
358 if (arrayIndex + this.inUse > array.Length)
359 throw new ArgumentException ("Not enough room from arrayIndex to end of array for this Hashtable");
361 IDictionaryEnumerator it = GetEnumerator ();
364 while (it.MoveNext ()) {
365 array.SetValue (it.Entry, i++);
372 public virtual void Add (Object key, Object value)
374 PutImpl (key, value, false);
378 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, CER.Success)]
380 public virtual void Clear ()
382 for (int i = 0;i<table.Length;i++) {
383 table [i].key = null;
384 table [i].value = null;
385 table [i].hashMix = 0;
392 public virtual bool Contains (Object key)
394 return (Find (key) >= 0);
397 public virtual IDictionaryEnumerator GetEnumerator ()
399 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
403 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, CER.Success)]
405 public virtual void Remove (Object key)
409 Slot [] table = this.table;
410 int h = table [i].hashMix;
412 table [i].hashMix = h;
413 table [i].key = (h != 0)
416 table [i].value = null;
422 public virtual bool ContainsKey (object key)
424 return Contains (key);
427 public virtual bool ContainsValue (object value)
429 int size = this.table.Length;
430 Slot [] table = this.table;
432 for (int i = 0; i < size; i++) {
433 Slot entry = table [i];
434 if (entry.key != null && entry.key!= KeyMarker.Removed
435 && entry.value == null) {
440 for (int i = 0; i < size; i++) {
441 Slot entry = table [i];
442 if (entry.key != null && entry.key!= KeyMarker.Removed
443 && value.Equals (entry.value)) {
454 public virtual object Clone ()
456 Hashtable ht = new Hashtable (Count, hcp, comparer);
458 ht.loadFactor = this.loadFactor;
460 // FIXME: maybe it's faster to simply
461 // copy the back-end array?
463 IDictionaryEnumerator it = GetEnumerator ();
464 while (it.MoveNext ()) {
465 ht [it.Key] = it.Value;
471 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
474 throw new ArgumentNullException ("info");
476 info.AddValue ("LoadFactor", loadFactor);
477 info.AddValue ("Version", modificationCount);
478 info.AddValue ("Comparer", comparerRef);
479 info.AddValue ("HashCodeProvider", hcpRef);
480 info.AddValue ("HashSize", this.table.Length);
482 Object [] keys = new Object[inUse];
483 CopyToArray(keys, 0, EnumeratorMode.KEY_MODE);
486 Object [] values = new Object[inUse];
487 CopyToArray(values, 0, EnumeratorMode.VALUE_MODE);
489 info.AddValue ("Keys", keys);
490 info.AddValue ("Values", values);
494 public virtual void OnDeserialization (object sender)
496 if (serializationInfo == null) return;
498 loadFactor = (float) serializationInfo.GetValue ("LoadFactor", typeof(float));
499 modificationCount = (int) serializationInfo.GetValue ("Version", typeof(int));
500 comparerRef = (IComparer) serializationInfo.GetValue ("Comparer", typeof (object));
501 hcpRef = (IHashCodeProvider) serializationInfo.GetValue ("HashCodeProvider", typeof (object));
502 int size = (int) serializationInfo.GetValue ("HashSize", typeof(int));
504 Object [] keys = (Object []) serializationInfo.GetValue("Keys", typeof(Object [] ));
505 Object [] values = (Object []) serializationInfo.GetValue("Values", typeof(Object [] ));
507 if (keys.Length != values.Length)
508 throw new SerializationException("Keys and values of uneven size");
510 size = ToPrime (size);
511 this.SetTable (new Slot [size]);
513 for(int i=0;i<keys.Length;i++)
514 Add(keys[i], values[i]);
518 serializationInfo = null;
522 /// Returns a synchronized (thread-safe)
523 /// wrapper for the Hashtable.
525 public static Hashtable Synchronized (Hashtable table)
528 throw new ArgumentNullException("table");
529 return new SyncHashtable (table);
535 // Protected instance methods
538 /// <summary>Returns the hash code for the specified key.</summary>
539 protected virtual int GetHash (Object key)
542 return key.GetHashCode ();
544 return hcpRef.GetHashCode (key);
548 /// Compares a specific Object with a specific key
549 /// in the Hashtable.
551 protected virtual bool KeyEquals (Object item, Object key)
553 if (comparerRef == null)
554 return item.Equals (key);
556 return comparerRef.Compare (item, key) == 0;
562 // Private instance methods
565 private void AdjustThreshold ()
567 int size = table.Length;
569 threshold = (int) (size*loadFactor);
570 if (this.threshold >= size)
574 private void SetTable (Slot [] table)
577 throw new ArgumentNullException ("table");
583 private int Find (Object key)
586 throw new ArgumentNullException ("key", "null key");
588 Slot [] table = this.table;
589 uint size = (uint) table.Length;
590 int h = this.GetHash (key) & Int32.MaxValue;
592 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
595 for (uint i = size; i > 0; i--) {
597 Slot entry = table [indx];
598 Object k = entry.key;
602 if (k == key || ((entry.hashMix & Int32.MaxValue) == h
603 && this.KeyEquals (key, k))) {
607 if ((entry.hashMix & CHAIN_MARKER) == 0)
616 private void Rehash ()
618 int oldSize = this.table.Length;
620 // From the SDK docs:
621 // Hashtable is automatically increased
622 // to the smallest prime number that is larger
623 // than twice the current number of Hashtable buckets
624 uint newSize = (uint)ToPrime ((oldSize<<1)|1);
627 Slot [] newTable = new Slot [newSize];
628 Slot [] table = this.table;
630 for (int i = 0;i<oldSize;i++) {
633 int h = s.hashMix & Int32.MaxValue;
635 uint step = ((uint) (h>>5)+1)% (newSize-1)+1;
636 for (uint j = spot%newSize;;spot+= step, j = spot%newSize) {
637 // No check for KeyMarker.Removed here,
638 // because the table is just allocated.
639 if (newTable [j].key == null) {
640 newTable [j].key = s.key;
641 newTable [j].value = s.value;
642 newTable [j].hashMix |= h;
645 newTable [j].hashMix |= CHAIN_MARKER;
651 ++this.modificationCount;
653 this.SetTable (newTable);
657 private void PutImpl (Object key, Object value, bool overwrite)
660 throw new ArgumentNullException ("key", "null key");
662 uint size = (uint)this.table.Length;
663 if (this.inUse >= this.threshold) {
665 size = (uint)this.table.Length;
668 int h = this.GetHash (key) & Int32.MaxValue;
670 uint step = (uint) ((spot>>5)+1)% (size-1)+1;
671 Slot [] table = this.table;
675 for (int i = 0; i < size; i++) {
676 int indx = (int) (spot % size);
677 entry = table [indx];
680 && entry.key == KeyMarker.Removed
681 && (entry.hashMix & CHAIN_MARKER) != 0)
684 if (entry.key == null ||
685 (entry.key == KeyMarker.Removed
686 && (entry.hashMix & CHAIN_MARKER) == 0)) {
693 if ((entry.hashMix & Int32.MaxValue) == h && KeyEquals (key, entry.key)) {
695 table [indx].value = value;
696 ++this.modificationCount;
699 // An entry with the same key already exists in the Hashtable.
700 throw new ArgumentException (
701 "Key duplication when adding: " + key);
706 if (freeIndx == -1) {
707 table [indx].hashMix |= CHAIN_MARKER;
715 table [freeIndx].key = key;
716 table [freeIndx].value = value;
717 table [freeIndx].hashMix |= h;
720 ++this.modificationCount;
725 private void CopyToArray (Array arr, int i,
728 IEnumerator it = new Enumerator (this, mode);
730 while (it.MoveNext ()) {
731 arr.SetValue (it.Current, i++);
738 // Private static methods
740 private static bool TestPrime (int x)
743 for (int n = 3; n< (int)Math.Sqrt (x); n += 2) {
749 // There is only one even prime - 2.
753 private static int CalcPrime (int x)
755 for (int i = (x & (~1))-1; i< Int32.MaxValue; i += 2) {
756 if (TestPrime (i)) return i;
761 private static int ToPrime (int x)
763 for (int i = 0; i < primeTbl.Length; i++) {
764 if (x <= primeTbl [i])
767 return CalcPrime (x);
777 private enum EnumeratorMode : int {KEY_MODE = 0, VALUE_MODE, ENTRY_MODE};
779 private sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
781 private Hashtable host;
785 private EnumeratorMode mode;
787 private Object currentKey;
788 private Object currentValue;
790 private readonly static string xstr = "Hashtable.Enumerator: snapshot out of sync.";
792 public Enumerator (Hashtable host, EnumeratorMode mode) {
794 stamp = host.modificationCount;
795 size = host.table.Length;
800 public Enumerator (Hashtable host)
801 : this (host, EnumeratorMode.KEY_MODE) {}
804 private void FailFast ()
806 if (host.modificationCount != stamp) {
807 throw new InvalidOperationException (xstr);
820 public bool MoveNext ()
825 while (++pos < size) {
826 Slot entry = host.table [pos];
828 if (entry.key != null && entry.key != KeyMarker.Removed) {
829 currentKey = entry.key;
830 currentValue = entry.value;
841 public DictionaryEntry Entry
844 if (currentKey == null) throw new InvalidOperationException ();
846 return new DictionaryEntry (currentKey, currentValue);
852 if (currentKey == null) throw new InvalidOperationException ();
858 public Object Value {
860 if (currentKey == null) throw new InvalidOperationException ();
866 public Object Current {
868 if (currentKey == null) throw new InvalidOperationException ();
871 case EnumeratorMode.KEY_MODE:
873 case EnumeratorMode.VALUE_MODE:
875 case EnumeratorMode.ENTRY_MODE:
876 return new DictionaryEntry (currentKey, currentValue);
878 throw new Exception ("should never happen");
885 private class HashKeys : ICollection, IEnumerable {
887 private Hashtable host;
889 public HashKeys (Hashtable host) {
891 throw new ArgumentNullException ();
898 public virtual int Count {
904 public virtual bool IsSynchronized {
906 return host.IsSynchronized;
910 public virtual Object SyncRoot {
911 get {return host.SyncRoot;}
914 public virtual void CopyTo (Array array, int arrayIndex)
917 throw new ArgumentNullException ("array");
919 throw new ArgumentException ("array");
921 throw new ArgumentOutOfRangeException ("arrayIndex");
922 if (array.Length - arrayIndex < Count)
923 throw new ArgumentException ("not enough space");
925 host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE);
930 public virtual IEnumerator GetEnumerator ()
932 return new Hashtable.Enumerator (host, EnumeratorMode.KEY_MODE);
937 private class HashValues : ICollection, IEnumerable {
939 private Hashtable host;
941 public HashValues (Hashtable host) {
943 throw new ArgumentNullException ();
950 public virtual int Count {
956 public virtual bool IsSynchronized {
958 return host.IsSynchronized;
962 public virtual Object SyncRoot {
964 return host.SyncRoot;
968 public virtual void CopyTo (Array array, int arrayIndex)
971 throw new ArgumentNullException ("array");
973 throw new ArgumentException ("array");
975 throw new ArgumentOutOfRangeException ("arrayIndex");
976 if (array.Length - arrayIndex < Count)
977 throw new ArgumentException ("not enough space");
979 host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE);
984 public virtual IEnumerator GetEnumerator ()
986 return new Hashtable.Enumerator (host, EnumeratorMode.VALUE_MODE);
992 private class SyncHashtable : Hashtable, IEnumerable {
994 private Hashtable host;
996 public SyncHashtable (Hashtable host) {
998 throw new ArgumentNullException ();
1003 internal SyncHashtable (SerializationInfo info, StreamingContext context)
1005 host = (Hashtable) info.GetValue("ParentTable", typeof(Hashtable));
1008 public override void GetObjectData (SerializationInfo info, StreamingContext context)
1010 info.AddValue ("ParentTable", host);
1015 public override int Count {
1021 public override bool IsSynchronized {
1027 public override Object SyncRoot {
1029 return host.SyncRoot;
1037 public override bool IsFixedSize {
1039 return host.IsFixedSize;
1044 public override bool IsReadOnly {
1046 return host.IsReadOnly;
1050 public override ICollection Keys {
1052 ICollection keys = null;
1053 lock (host.SyncRoot) {
1060 public override ICollection Values {
1062 ICollection vals = null;
1063 lock (host.SyncRoot) {
1072 public override Object this [Object key] {
1077 lock (host.SyncRoot) {
1085 IEnumerator IEnumerable.GetEnumerator ()
1087 return new Enumerator (host, EnumeratorMode.KEY_MODE);
1095 public override void CopyTo (Array array, int arrayIndex)
1097 host.CopyTo (array, arrayIndex);
1103 public override void Add (Object key, Object value)
1105 lock (host.SyncRoot) {
1106 host.Add (key, value);
1110 public override void Clear ()
1112 lock (host.SyncRoot) {
1117 public override bool Contains (Object key)
1119 return (host.Find (key) >= 0);
1122 public override IDictionaryEnumerator GetEnumerator ()
1124 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1127 public override void Remove (Object key)
1129 lock (host.SyncRoot) {
1136 public override bool ContainsKey (object key)
1138 return host.Contains (key);
1141 public override bool ContainsValue (object value)
1143 return host.ContainsValue (value);
1149 public override object Clone ()
1151 lock(host.SyncRoot) {
1152 return new SyncHashtable( (Hashtable) host.Clone () );