接下来我们一步步来熟悉 Dictionary的底层结构实现,下面的MyDictionary等同于源码中的Dictionary看待。
首先我们定义一个类 MyDictionary,类中定义一个结构Entry,用来存储我们的key和value,除此之外,我们还要定义一个int变量用于存储key的hashCode,因为我们是根据hash值来查找的,然后我们再定义一个next变量 用于内部指向同一个桶下的下一个Entry(发生桶碰撞时,采用链式把冲突的链起来,即拉链法)
public class MyDictionary<TKey, TValue> { ....... private struct Entry { public int hashCode; // 31 位 hash code, -1 表示未用 public int next; // 下一个Entry的索引位置 -1 表示链最后一个 public TKey key; // Key of entry public TValue value; // Value of entry } ..... }
接下来接下来我们一个个加 其他字段,先简单说下这个hashCode到时候是怎么赋值的
我们源代码中是
int hashCode =comparer.GetHashCode(key) & 0x7FFFFFFF;
而 comparer是我们定义的一个类型为 IEqualityComparer<TKey> 的私有变量,而这个私有变量的赋值是在我们MyDictionary构造函数中赋值的
构造函数有好多重载方法,主要参数有两个,一个是初始容量,另一个就是这个IEqualityComparer<TKey> comparer
假设我们定义一个 var dic=Dictionary<int,string>();
public MyDictionary(): this(0, null) //走这里 { } public MyDictionary(int capacity): this(capacity, null) { } public MyDictionary(IEqualityComparer<TKey> comparer): this(0, comparer) { } public MyDictionary(int capacity, IEqualityComparer<TKey> comparer) //然后到这里 { if (capacity < 0) { throw new Exception("capacity异常"); } if (capacity > 0) Initialize(capacity); //初始化容器
this.comparer = comparer ?? EqualityComparer<TKey>.Default; //然后到这里 }
可以看到,我们没有指定它,这个comparer 用的是 EqualityComparer<TKey>.Default;
Dictioanry内部的比较都是通过这个实例来进行的。我们继续看它源码级别的定义
public abstract class EqualityComparer<T> : IEqualityComparer, IEqualityComparer<T> { static readonly EqualityComparer<T> defaultComparer = CreateComparer(); public static EqualityComparer<T> Default { get { return defaultComparer; } } private static EqualityComparer<T> CreateComparer() //走到这里 { RuntimeType t = (RuntimeType)typeof(T); // Specialize type byte for performance reasons if (t == typeof(byte)) { return (EqualityComparer<T>)(object)(new ByteEqualityComparer()); } // If T implements IEquatable<T> return a GenericEqualityComparer<T> if (typeof(IEquatable<T>).IsAssignableFrom(t)) { return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), t); } if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; if (typeof(IEquatable<>).MakeGenericType(u).IsAssignableFrom(u)) { return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableEqualityComparer<int>), u); } } // See the METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST and METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST_LONG cases in getILIntrinsicImplementation if (t.IsEnum) { TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t));
switch (underlyingTypeCode)
{
case TypeCode.Int16: // short
return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ShortEnumEqualityComparer<short>), t);
case TypeCode.SByte:
return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(SByteEnumEqualityComparer<sbyte>), t);
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Byte:
case TypeCode.UInt16: //ushort
return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<int>), t);
case TypeCode.Int64:
case TypeCode.UInt64:
return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(LongEnumEqualityComparer<long>), t);
}
} // Otherwise return an ObjectEqualityComparer<T> return new ObjectEqualityComparer<T>(); }
这个大致可以概括为:在CreateComparer
我们可以看到如果我们的类型不是byte
、没实现IEquatable<T>
接口、不是Nullable<T>
、不是enum
的话,会默认给我们创建一个ObjectEqualityComparer<T>()
。
ObjectEqualityComparer<T>(); 这个的源码定义为:
internal class ObjectEqualityComparer<T> : EqualityComparer<T> { public override bool Equals(T x, T y) { if (x != null) { if (y != null) return x.Equals(y); return false; } if (y != null) return false; return true; } public override int GetHashCode(T obj) { if (obj == null) return 0; return obj.GetHashCode(); } // Equals method for the comparer itself. public override bool Equals(Object obj) { ObjectEqualityComparer<T> comparer = obj as ObjectEqualityComparer<T>; return comparer != null; } public override int GetHashCode() { return this.GetType().Name.GetHashCode(); } }
要注意的是 这个 ObjectEqualityComparer 下的Equal方法 对于像值类型是有装箱操作的
我们使用Dictionary的时候一般的习惯应该就上面那样用,这种使用方法在我们使用内置的类型当key的时候没有问题,但是如果我们需要将一个自定义的值类型(struct)当作key的时候就需要注意了。这里有一个很容易忽略的问题,会导致使用Dictionary的时候带来大量不必要的性能开销。
我们先做一个实验来比较一下值类型和类作为key的性能有多大的差距。实验代码如下,这段代码中我插入1000个到10000个数据来得到所需要的时间。
ublic class/struct CustomKey { public int Field1; public int Field2; public override int GetHashCode() { return Field1.GetHashCode() ^ Field2.GetHashCode(); } public override bool Equals(object obj) { CustomKey key = (CustomKey)obj; return this.Field1 == key.Field1 && this.Field2 == key.Field2; } } Dictionary<CustomKey, int> dict = new Dictionary<CustomKey, int>(); int tryCount = 50; double totalTime = 0.0; for (int count = 1000; count < 10000; count += 1000) { for (int j = 0; j < tryCount; j++) { Stopwatch watcher = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { CustomKey key = new CustomKey() { Field1 = i * 2, Field2 = i * 2 + 1 }; dict.Add(key, i); } watcher.Stop(); dict.Clear(); totalTime += watcher.ElapsedMilliseconds; } Console.WriteLine("{0},{1}", count, totalTime / tryCount); }
结果是这样子的:
原因就在于:ObjectEqualityComparer的默认实现中会存在着很多的装箱操作,它是用来将值类型装箱成引用类型的。这个操作是很耗时的,因为它需要创建一个object并将值类型中的值拷贝到新创建的对象中。