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;
78 private SerializationInfo serializationInfo;
80 private static readonly int [] primeTbl = {
121 public Hashtable () : this (0, 1.0f) {}
124 public Hashtable (int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
126 throw new ArgumentOutOfRangeException ("capacity", "negative capacity");
128 if (loadFactor < 0.1f || loadFactor > 1.0f || Single.IsNaN (loadFactor))
129 throw new ArgumentOutOfRangeException ("loadFactor", "load factor");
131 if (capacity == 0) ++capacity;
132 this.loadFactor = 0.75f*loadFactor;
133 double tableSize = capacity / this.loadFactor;
135 if (tableSize > Int32.MaxValue)
136 throw new ArgumentException ("Size is too big");
138 int size = (int) tableSize;
139 size = ToPrime (size);
140 this.SetTable (new Slot [size]);
143 this.comparer = comparer;
146 this.modificationCount = 0;
150 public Hashtable (int capacity, float loadFactor) :
151 this (capacity, loadFactor, null, null)
155 public Hashtable (int capacity) : this (capacity, 1.0f)
159 public Hashtable (int capacity,
160 IHashCodeProvider hcp,
162 : this (capacity, 1.0f, hcp, comparer)
167 public Hashtable (IDictionary d, float loadFactor,
168 IHashCodeProvider hcp, IComparer comparer)
169 : this (d!=null ? d.Count : 0,
170 loadFactor, hcp, comparer)
174 throw new ArgumentNullException ("dictionary");
176 IDictionaryEnumerator it = d.GetEnumerator ();
177 while (it.MoveNext ()) {
178 Add (it.Key, it.Value);
183 public Hashtable (IDictionary d, float loadFactor)
184 : this (d, loadFactor, null, null)
189 public Hashtable (IDictionary d) : this (d, 1.0f)
193 public Hashtable (IDictionary d, IHashCodeProvider hcp, IComparer comparer)
194 : this (d, 1.0f, hcp, comparer)
198 public Hashtable (IHashCodeProvider hcp, IComparer comparer)
199 : this (1, 1.0f, hcp, comparer)
203 protected Hashtable (SerializationInfo info, StreamingContext context)
205 serializationInfo = info;
212 protected IComparer comparer {
221 protected IHashCodeProvider hcp {
232 public virtual int Count {
238 public virtual bool IsSynchronized {
244 public virtual Object SyncRoot {
254 public virtual bool IsFixedSize {
261 public virtual bool IsReadOnly {
267 public virtual ICollection Keys {
269 if (this.hashKeys == null)
270 this.hashKeys = new HashKeys (this);
271 return this.hashKeys;
275 public virtual ICollection Values {
277 if (this.hashValues == null)
278 this.hashValues = new HashValues (this);
279 return this.hashValues;
285 public virtual Object this [Object key] {
287 return GetImpl (key);
290 PutImpl (key, value, true);
304 IEnumerator IEnumerable.GetEnumerator ()
306 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
311 public virtual void CopyTo (Array array, int arrayIndex)
314 throw new ArgumentNullException ("array");
317 throw new ArgumentOutOfRangeException ("arrayIndex");
320 throw new ArgumentException ("array is multidimensional");
322 if ((array.Length > 0) && (arrayIndex >= array.Length))
323 throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
325 if (arrayIndex + this.inUse > array.Length)
326 throw new ArgumentException ("Not enough room from arrayIndex to end of array for this Hashtable");
328 IDictionaryEnumerator it = GetEnumerator ();
331 while (it.MoveNext ()) {
332 array.SetValue (it.Entry, i++);
339 public virtual void Add (Object key, Object value)
341 PutImpl (key, value, false);
344 public virtual void Clear ()
346 for (int i = 0;i<table.Length;i++) {
347 table [i].key = null;
348 table [i].value = null;
349 table [i].hashMix = 0;
356 public virtual bool Contains (Object key)
358 return (Find (key) >= 0);
361 public virtual IDictionaryEnumerator GetEnumerator ()
363 return new Enumerator (this, EnumeratorMode.ENTRY_MODE);
366 public virtual void Remove (Object key)
370 Slot [] table = this.table;
371 int h = table [i].hashMix;
373 table [i].hashMix = h;
374 table [i].key = (h != 0)
377 table [i].value = null;
383 public virtual bool ContainsKey (object key)
385 return Contains (key);
388 public virtual bool ContainsValue (object value)
390 int size = this.table.Length;
391 Slot [] table = this.table;
393 for (int i = 0; i < size; i++) {
394 Slot entry = table [i];
395 if (entry.key != null && entry.key!= KeyMarker.Removed
396 && entry.value == null) {
401 for (int i = 0; i < size; i++) {
402 Slot entry = table [i];
403 if (entry.key != null && entry.key!= KeyMarker.Removed
404 && value.Equals (entry.value)) {
415 public virtual object Clone ()
417 Hashtable ht = new Hashtable (Count, hcp, comparer);
419 ht.loadFactor = this.loadFactor;
421 // FIXME: maybe it's faster to simply
422 // copy the back-end array?
424 IDictionaryEnumerator it = GetEnumerator ();
425 while (it.MoveNext ()) {
426 ht [it.Key] = it.Value;
432 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
435 throw new ArgumentNullException ("info");
437 info.AddValue ("LoadFactor", loadFactor);
438 info.AddValue ("Version", modificationCount);
439 info.AddValue ("Comparer", comparerRef);
440 info.AddValue ("HashCodeProvider", hcpRef);
441 info.AddValue ("HashSize", this.table.Length);
443 Object [] keys = new Object[inUse];
444 CopyToArray(keys, 0, EnumeratorMode.KEY_MODE);
447 Object [] values = new Object[inUse];
448 CopyToArray(values, 0, EnumeratorMode.VALUE_MODE);
450 info.AddValue ("Keys", keys);
451 info.AddValue ("Values", values);
455 public virtual void OnDeserialization (object sender)
457 if (serializationInfo == null) return;
459 loadFactor = (float) serializationInfo.GetValue ("LoadFactor", typeof(float));
460 modificationCount = (int) serializationInfo.GetValue ("Version", typeof(int));
461 comparerRef = (IComparer) serializationInfo.GetValue ("Comparer", typeof (object));
462 hcpRef = (IHashCodeProvider) serializationInfo.GetValue ("HashCodeProvider", typeof (object));
463 int size = (int) serializationInfo.GetValue ("HashSize", typeof(int));
465 Object [] keys = (Object []) serializationInfo.GetValue("Keys", typeof(Object [] ));
466 Object [] values = (Object []) serializationInfo.GetValue("Values", typeof(Object [] ));
468 if (keys.Length != values.Length)
469 throw new SerializationException("Keys and values of uneven size");
471 size = ToPrime (size);
472 this.SetTable (new Slot [size]);
474 for(int i=0;i<keys.Length;i++)
475 Add(keys[i], values[i]);
479 serializationInfo = null;
483 /// Returns a synchronized (thread-safe)
484 /// wrapper for the Hashtable.
486 public static Hashtable Synchronized (Hashtable table)
489 throw new ArgumentNullException("table");
490 return new SyncHashtable (table);
496 // Protected instance methods
499 /// <summary>Returns the hash code for the specified key.</summary>
500 protected virtual int GetHash (Object key)
502 IHashCodeProvider hcp = this.hcp;
504 ? hcp.GetHashCode (key)
505 : key.GetHashCode ();
509 /// Compares a specific Object with a specific key
510 /// in the Hashtable.
512 protected virtual bool KeyEquals (Object item, Object key)
514 IComparer c = this.comparer;
516 return (c.Compare (item, key) == 0);
518 return item.Equals (key);
524 // Private instance methods
527 private void AdjustThreshold ()
529 int size = table.Length;
531 threshold = (int) (size*loadFactor);
532 if (this.threshold >= size)
536 private void SetTable (Slot [] table)
539 throw new ArgumentNullException ("table");
545 private Object GetImpl (Object key)
550 return table [i].value;
556 private int Find (Object key)
559 throw new ArgumentNullException ("key", "null key");
561 uint size = (uint) this.table.Length;
562 int h = this.GetHash (key) & Int32.MaxValue;
564 uint step = (uint) ((h >> 5)+1) % (size-1)+1;
565 Slot[] table = this.table;
567 for (uint i = 0; i < size;i++) {
568 int indx = (int) (spot % size);
569 Slot entry = table [indx];
570 Object k = entry.key;
573 if (k == key || ((entry.hashMix & Int32.MaxValue) == h
574 && this.KeyEquals (key, k))) {
578 if ((entry.hashMix & CHAIN_MARKER) == 0)
587 private void Rehash ()
589 int oldSize = this.table.Length;
591 // From the SDK docs:
592 // Hashtable is automatically increased
593 // to the smallest prime number that is larger
594 // than twice the current number of Hashtable buckets
595 uint newSize = (uint)ToPrime ((oldSize<<1)|1);
598 Slot [] newTable = new Slot [newSize];
599 Slot [] table = this.table;
601 for (int i = 0;i<oldSize;i++) {
604 int h = s.hashMix & Int32.MaxValue;
606 uint step = ((uint) (h>>5)+1)% (newSize-1)+1;
607 for (uint j = spot%newSize;;spot+= step, j = spot%newSize) {
608 // No check for KeyMarker.Removed here,
609 // because the table is just allocated.
610 if (newTable [j].key == null) {
611 newTable [j].key = s.key;
612 newTable [j].value = s.value;
613 newTable [j].hashMix |= h;
616 newTable [j].hashMix |= CHAIN_MARKER;
622 ++this.modificationCount;
624 this.SetTable (newTable);
628 private void PutImpl (Object key, Object value, bool overwrite)
631 throw new ArgumentNullException ("key", "null key");
633 uint size = (uint)this.table.Length;
634 if (this.inUse >= this.threshold) {
636 size = (uint)this.table.Length;
639 int h = this.GetHash (key) & Int32.MaxValue;
641 uint step = (uint) ((spot>>5)+1)% (size-1)+1;
642 Slot [] table = this.table;
646 for (int i = 0; i < size; i++) {
647 int indx = (int) (spot % size);
648 entry = table [indx];
651 && entry.key == KeyMarker.Removed
652 && (entry.hashMix & CHAIN_MARKER) != 0)
655 if (entry.key == null ||
656 (entry.key == KeyMarker.Removed
657 && (entry.hashMix & CHAIN_MARKER) == 0)) {
664 if ((entry.hashMix & Int32.MaxValue) == h && KeyEquals (key, entry.key)) {
666 table [indx].value = value;
667 ++this.modificationCount;
670 // An entry with the same key already exists in the Hashtable.
671 throw new ArgumentException (
672 "Key duplication when adding: " + key);
677 if (freeIndx == -1) {
678 table [indx].hashMix |= CHAIN_MARKER;
686 table [freeIndx].key = key;
687 table [freeIndx].value = value;
688 table [freeIndx].hashMix |= h;
691 ++this.modificationCount;
696 private void CopyToArray (Array arr, int i,
699 IEnumerator it = new Enumerator (this, mode);
701 while (it.MoveNext ()) {
702 arr.SetValue (it.Current, i++);
709 // Private static methods
711 private static bool TestPrime (int x)
714 for (int n = 3; n< (int)Math.Sqrt (x); n += 2) {
720 // There is only one even prime - 2.
724 private static int CalcPrime (int x)
726 for (int i = (x & (~1))-1; i< Int32.MaxValue; i += 2) {
727 if (TestPrime (i)) return i;
732 private static int ToPrime (int x)
734 for (int i = 0; i < primeTbl.Length; i++) {
735 if (x <= primeTbl [i])
738 return CalcPrime (x);
748 private enum EnumeratorMode : int {KEY_MODE = 0, VALUE_MODE, ENTRY_MODE};
750 private sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
752 private Hashtable host;
756 private EnumeratorMode mode;
758 private Object currentKey;
759 private Object currentValue;
761 private readonly static string xstr = "Hashtable.Enumerator: snapshot out of sync.";
763 public Enumerator (Hashtable host, EnumeratorMode mode) {
765 stamp = host.modificationCount;
766 size = host.table.Length;
771 public Enumerator (Hashtable host)
772 : this (host, EnumeratorMode.KEY_MODE) {}
775 private void FailFast ()
777 if (host.modificationCount != stamp) {
778 throw new InvalidOperationException (xstr);
791 public bool MoveNext ()
796 while (++pos < size) {
797 Slot entry = host.table [pos];
799 if (entry.key != null && entry.key != KeyMarker.Removed) {
800 currentKey = entry.key;
801 currentValue = entry.value;
812 public DictionaryEntry Entry
815 if (currentKey == null) throw new InvalidOperationException ();
817 return new DictionaryEntry (currentKey, currentValue);
823 if (currentKey == null) throw new InvalidOperationException ();
829 public Object Value {
831 if (currentKey == null) throw new InvalidOperationException ();
837 public Object Current {
839 if (currentKey == null) throw new InvalidOperationException ();
842 case EnumeratorMode.KEY_MODE:
844 case EnumeratorMode.VALUE_MODE:
846 case EnumeratorMode.ENTRY_MODE:
847 return new DictionaryEntry (currentKey, currentValue);
849 throw new Exception ("should never happen");
856 private class HashKeys : ICollection, IEnumerable {
858 private Hashtable host;
860 public HashKeys (Hashtable host) {
862 throw new ArgumentNullException ();
869 public virtual int Count {
875 public virtual bool IsSynchronized {
877 return host.IsSynchronized;
881 public virtual Object SyncRoot {
882 get {return host.SyncRoot;}
885 public virtual void CopyTo (Array array, int arrayIndex)
888 throw new ArgumentNullException ("array");
890 throw new ArgumentException ("array");
892 throw new ArgumentOutOfRangeException ("arrayIndex");
893 if (array.Length - arrayIndex < Count)
894 throw new ArgumentException ("not enough space");
896 host.CopyToArray (array, arrayIndex, EnumeratorMode.KEY_MODE);
901 public virtual IEnumerator GetEnumerator ()
903 return new Hashtable.Enumerator (host, EnumeratorMode.KEY_MODE);
908 private class HashValues : ICollection, IEnumerable {
910 private Hashtable host;
912 public HashValues (Hashtable host) {
914 throw new ArgumentNullException ();
921 public virtual int Count {
927 public virtual bool IsSynchronized {
929 return host.IsSynchronized;
933 public virtual Object SyncRoot {
935 return host.SyncRoot;
939 public virtual void CopyTo (Array array, int arrayIndex)
942 throw new ArgumentNullException ("array");
944 throw new ArgumentException ("array");
946 throw new ArgumentOutOfRangeException ("arrayIndex");
947 if (array.Length - arrayIndex < Count)
948 throw new ArgumentException ("not enough space");
950 host.CopyToArray (array, arrayIndex, EnumeratorMode.VALUE_MODE);
955 public virtual IEnumerator GetEnumerator ()
957 return new Hashtable.Enumerator (host, EnumeratorMode.VALUE_MODE);
963 private class SyncHashtable : Hashtable, IEnumerable {
965 private Hashtable host;
967 public SyncHashtable (Hashtable host) {
969 throw new ArgumentNullException ();
974 internal SyncHashtable (SerializationInfo info, StreamingContext context)
976 host = (Hashtable) info.GetValue("ParentTable", typeof(Hashtable));
979 public override void GetObjectData (SerializationInfo info, StreamingContext context)
981 info.AddValue ("ParentTable", host);
986 public override int Count {
992 public override bool IsSynchronized {
998 public override Object SyncRoot {
1000 return host.SyncRoot;
1008 public override bool IsFixedSize {
1010 return host.IsFixedSize;
1015 public override bool IsReadOnly {
1017 return host.IsReadOnly;
1021 public override ICollection Keys {
1023 ICollection keys = null;
1024 lock (host.SyncRoot) {
1031 public override ICollection Values {
1033 ICollection vals = null;
1034 lock (host.SyncRoot) {
1043 public override Object this [Object key] {
1045 return host.GetImpl (key);
1048 lock (host.SyncRoot) {
1049 host.PutImpl (key, value, true);
1056 IEnumerator IEnumerable.GetEnumerator ()
1058 return new Enumerator (host, EnumeratorMode.KEY_MODE);
1066 public override void CopyTo (Array array, int arrayIndex)
1068 host.CopyTo (array, arrayIndex);
1074 public override void Add (Object key, Object value)
1076 lock (host.SyncRoot) {
1077 host.PutImpl (key, value, false);
1081 public override void Clear ()
1083 lock (host.SyncRoot) {
1088 public override bool Contains (Object key)
1090 return (host.Find (key) >= 0);
1093 public override IDictionaryEnumerator GetEnumerator ()
1095 return new Enumerator (host, EnumeratorMode.ENTRY_MODE);
1098 public override void Remove (Object key)
1100 lock (host.SyncRoot) {
1107 public override bool ContainsKey (object key)
1109 return host.Contains (key);
1112 public override bool ContainsValue (object value)
1114 return host.ContainsValue (value);
1120 public override object Clone ()
1122 lock(host.SyncRoot) {
1123 return new SyncHashtable( (Hashtable) host.Clone () );