C# Dictionary<T>源码浅析

所在框架版本:.Net Framework 4.0.30319
桶数组中的值是对应Entry数组的下标,Entry的Next是下一个Entry元素的下标

1.Entry结构体,键值对存放的位置

private struct Entry
    {
      public int hashCode;  // 除符号位以外的31位hashCode值, 如果该Entry没有被使用,那么为-1
      public int next;   //下一个Entry元素的下标索引,如果没有下一个就为-1
      public TKey key;   //存放元素的键
      public TValue value;   // 存放元素的值
    }

2.部分变量

	private int[] buckets;    // Hash桶
    private Dictionary<TKey, TValue>.Entry[] entries;   // Entry数组,存放元素
    private int count;            // 已有的键值对个数
    private int version;     // 当前版本,防止迭代过程中集合被更改
    private int freeList;    // 被删除Entry在entries中的下标,代表这个位置是空闲的
    private int freeCount;   // 有多少个被删除的Entry,有多少个空闲的位置
    private IEqualityComparer<TKey> comparer;    // 比较器
    private Dictionary<TKey, TValue>.KeyCollection keys;    // Key的集合
    private Dictionary<TKey, TValue>.ValueCollection values;   // Value的集合

3.初始化

private void Initialize(int capacity)   //参数是初始容量
    {
      int prime = HashHelpers.GetPrime(capacity);   //得到大于容量的最小质数作为最终容量
      this.buckets = new int[prime];           //初始化桶数组
      for (int index = 0; index < this.buckets.Length; ++index)  
        this.buckets[index] = -1;    //每个桶默认值为-1
      this.entries = new Dictionary<TKey, TValue>.Entry[prime];  //初始化Entry数组
      this.freeList = -1;    //freeList<0,代表Entry数组中没有空闲
    }

4.插入

private void Insert(TKey key, TValue value, bool add)  //键,值,是否是添加操作
    {
      if ((object) key == null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
      if (this.buckets == null)     //如果桶数组为空,重新初始化
        this.Initialize(0);
      int num1 = this.comparer.GetHashCode(key) & int.MaxValue;   //通过key获取hash值,&这样的操作通常用于确保哈希码为正数,即将哈希码限制在非负整数范围内
      int index1 = num1 % this.buckets.Length;   //hash值%桶数组的长度得到在哪个桶中,即桶数组的下标
      int num2 = 0;   //hash碰撞次数
      for (int index2 = this.buckets[index1]; index2 >= 0; index2 = this.entries[index2].next)  //遍历判断传入的键是否已经存在
      {
        if (this.entries[index2].hashCode == num1 && this.comparer.Equals(this.entries[index2].key, key))   
        {
          if (add)  //如果是Add操作,抛出异常
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
          this.entries[index2].value = value;   //如果不是Add操作,比如dic[1] = 2这种情况,直接赋新值
          ++this.version;  //版本号更新
          return;
        }
        ++num2;
      }
      int index3;   //Entry数组的下标
      if (this.freeCount > 0)   //freeCount>0,说明之前有删除元素
      {
        index3 = this.freeList;    //将被删除元素的Entry下标给到新元素
        this.freeList = this.entries[index3].next;  //freeList 更新为当前元素的next Entry数组下标
        --this.freeCount;  //空闲个数减一
      }
      else
      {
        if (this.count == this.entries.Length)  //如果Entry数组已满
        {
          this.Resize();  //扩容
          index1 = num1 % this.buckets.Length;   //重新获得对应桶的下标
        }
        index3 = this.count;  //默认取Entry数组中未使用的第一个
        ++this.count;    
      }
      this.entries[index3].hashCode = num1;     // 给当前Entry赋值
      this.entries[index3].next = this.buckets[index1];
      this.entries[index3].key = key;
      this.entries[index3].value = value;
      this.buckets[index1] = index3;   //桶数组的值变成当前Entry元素的下标
      ++this.version;    //版本号更新
	  // 如果碰撞次数大于设置的最大碰撞次数,那么触发Hash碰撞扩容
      if (num2 <= 100 || !HashHelpers.IsWellKnownEqualityComparer((object) this.comparer))  
        return;
      this.comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer((object) this.comparer);
      this.Resize(this.entries.Length, true);
    }

5.查找

private int FindEntry(TKey key)
    {
      if ((object) key == null)   //如果查找键为空,抛出异常
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
      if (this.buckets != null) 
      {
        int num = this.comparer.GetHashCode(key) & int.MaxValue;   //得到键对应的hash值
        for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
        {   //遍历entries链
          if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))   //如果查找到了对应的键,直接返回对应的Entry元素下标
            return index;
        }
      }
      return -1;
    }
public bool TryGetValue(TKey key, out TValue value)
    {
      int entry = this.FindEntry(key);
      if (entry >= 0)
      {
        value = this.entries[entry].value;   //通过FindEntry查找到的下标,找到对应的值
        return true;
      }
      value = default (TValue);
      return false;
    }
public bool ContainsKey(TKey key) => this.FindEntry(key) >= 0;   //直接看FindEntry是否找到下标   
public bool ContainsValue(TValue value)
    {
      if ((object) value == null)   //如果要查找的值为空
      {
        for (int index = 0; index < this.count; ++index)   //遍历entries数组
        {
          if (this.entries[index].hashCode >= 0 && (object) this.entries[index].value == null)  //如果hash值大于0且值为空,说明找到了
            return true;
        }
      }
      else
      {
        EqualityComparer<TValue> equalityComparer = EqualityComparer<TValue>.Default;
        for (int index = 0; index < this.count; ++index)   //依旧遍历entries数组
        {
          if (this.entries[index].hashCode >= 0 && equalityComparer.Equals(this.entries[index].value, value))   //看hash值的同时也看值是否相等
            return true;
        }
      }
      return false;
    }

6.移除

public bool Remove(TKey key)
    {
      if ((object) key == null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
      if (this.buckets != null)
      {
        int num = this.comparer.GetHashCode(key) & int.MaxValue;   //得到hash值
        int index1 = num % this.buckets.Length;    //获得对应的桶
        int index2 = -1;   // 用于确定是否当前entries链的第一个元素
        for (int index3 = this.buckets[index1]; index3 >= 0; index3 = this.entries[index3].next)  //遍历entries链
        {
          if (this.entries[index3].hashCode == num && this.comparer.Equals(this.entries[index3].key, key))   //如果找到了对应的键
          {
            if (index2 < 0)    //如果要移除的是第一个元素,则对应桶存储当前元素的Next元素下标
              this.buckets[index1] = this.entries[index3].next;
            else    //不是第一个,需要将上一个元素的Next指向当前元素的Next,相当于越过了当前元素
              this.entries[index2].next = this.entries[index3].next;
             //将Entry内的数据初始化
            this.entries[index3].hashCode = -1;
            this.entries[index3].next = this.freeList;
            this.entries[index3].key = default (TKey);
            this.entries[index3].value = default (TValue);
            this.freeList = index3;   //freeList等于当前的Entry元素的位置,下一次添加元素会优先添加到该位置
            ++this.freeCount;   //空闲个数加一
            ++this.version;   //版本加一
            return true;
          }
          index2 = index3;    //不断在这个链表上往后移动
        }
      }
      return false;
    }

7.扩容

private void Resize() => this.Resize(HashHelpers.ExpandPrime(this.count), false);

private void Resize(int newSize, bool forceNewHashCodes)
    {
      int[] numArray = new int[newSize];   //申请新的桶数组
      for (int index = 0; index < numArray.Length; ++index)
        numArray[index] = -1;
      Dictionary<TKey, TValue>.Entry[] entryArray = new Dictionary<TKey, TValue>.Entry[newSize];  //申请新的Entry数组
      Array.Copy((Array) this.entries, 0, (Array) entryArray, 0, this.count);   //将原来的Entry数组的内容拷贝到新的Entry数组
      if (forceNewHashCodes)   //如果是hash碰撞扩容,需要重新计算hash值(指只用了一个桶,但是Entry数组被使用完的情况)
      {
        for (int index = 0; index < this.count; ++index)
        {
          if (entryArray[index].hashCode != -1)
            entryArray[index].hashCode = this.comparer.GetHashCode(entryArray[index].key) & int.MaxValue;
        }
      }
      for (int index1 = 0; index1 < this.count; ++index1)   //遍历原来的Entry数组,重新建立Entry链表
      {
        if (entryArray[index1].hashCode >= 0)
        {
          int index2 = entryArray[index1].hashCode % newSize;   //获得新的桶数组下标
          entryArray[index1].next = numArray[index2];      //重新建立entries 链表
          numArray[index2] = index1;
        }
      }
     //将数组赋值会原来
      this.buckets = numArray;    
      this.entries = entryArray;
    }

8.版本控制
字典类内部实现了迭代器相关的方法

public struct Enumerator : 
      IEnumerator<KeyValuePair<TKey, TValue>>,
      IDisposable,
      IEnumerator,
      IDictionaryEnumerator
    {
      private Dictionary<TKey, TValue> dictionary;
      private int version;
      private int index;
      private KeyValuePair<TKey, TValue> current;
      private int getEnumeratorRetType;
      internal const int DictEntry = 1;
      internal const int KeyValuePair = 2;

      internal Enumerator(Dictionary<TKey, TValue> dictionary, int getEnumeratorRetType)   //构造函数
      {
        this.dictionary = dictionary;
        this.version = dictionary.version;   //版本
        this.index = 0;
        this.getEnumeratorRetType = getEnumeratorRetType;
        this.current = new KeyValuePair<TKey, TValue>();
      }

      [__DynamicallyInvokable]
      public bool MoveNext()      //MoveNext方法
      {
        if (this.version != this.dictionary.version)
          ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
        for (; (uint) this.index < (uint) this.dictionary.count; ++this.index)
        {
          if (this.dictionary.entries[this.index].hashCode >= 0)
          {
            this.current = new KeyValuePair<TKey, TValue>(this.dictionary.entries[this.index].key, this.dictionary.entries[this.index].value);
            ++this.index;
            return true;
          }
        }
        this.index = this.dictionary.count + 1;
        this.current = new KeyValuePair<TKey, TValue>();
        return false;
      }

      [__DynamicallyInvokable]
      public KeyValuePair<TKey, TValue> Current      //返回current
      {
        [__DynamicallyInvokable] get => this.current;
      }

      [__DynamicallyInvokable]
      public void Dispose()
      {
      }

      [__DynamicallyInvokable]
      object IEnumerator.Current
      {
        [__DynamicallyInvokable] get
        {
          if (this.index == 0 || this.index == this.dictionary.count + 1)
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
          return this.getEnumeratorRetType == 1 ? (object) new DictionaryEntry((object) this.current.Key, (object) this.current.Value) : (object) new KeyValuePair<TKey, TValue>(this.current.Key, this.current.Value);
        }
      }

      [__DynamicallyInvokable]
      void IEnumerator.Reset()           //Reset方法
      {
        if (this.version != this.dictionary.version)
          ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
        this.index = 0;
        this.current = new KeyValuePair<TKey, TValue>();
      }

      [__DynamicallyInvokable]
      DictionaryEntry IDictionaryEnumerator.Entry
      {
        [__DynamicallyInvokable] get
        {
          if (this.index == 0 || this.index == this.dictionary.count + 1)
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
          return new DictionaryEntry((object) this.current.Key, (object) this.current.Value);
        }
      }

      [__DynamicallyInvokable]
      object IDictionaryEnumerator.Key
      {
        [__DynamicallyInvokable] get
        {
          if (this.index == 0 || this.index == this.dictionary.count + 1)
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
          return (object) this.current.Key;
        }
      }

      [__DynamicallyInvokable]
      object IDictionaryEnumerator.Value
      {
        [__DynamicallyInvokable] get
        {
          if (this.index == 0 || this.index == this.dictionary.count + 1)
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
          return (object) this.current.Value;
        }
      }
    }

9.清除

public void Clear()
    {
      if (this.count <= 0)
        return;
      for (int index = 0; index < this.buckets.Length; ++index)
        this.buckets[index] = -1;     //重置桶数组
      Array.Clear((Array) this.entries, 0, this.count);   //重置Entry数组
      this.freeList = -1;  //重置freeList 
      this.count = 0;  
      this.freeCount = 0;
      ++this.version;   //版本号加一
    }

10.键值对数量

public int Count
      {
        [__DynamicallyInvokable] get => this.dictionary.Count;  //直接返回count
      }

11.复制

public void CopyTo(TKey[] array, int index)   
      {
        if (array == null)
          ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
        if (index < 0 || index > array.Length)
          ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
        if (array.Length - index < this.dictionary.Count)   //要存放的空间太小,放不下
          ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
        int count = this.dictionary.count;
        Dictionary<TKey, TValue>.Entry[] entries = this.dictionary.entries;
        for (int index1 = 0; index1 < count; ++index1)
        {
          if (entries[index1].hashCode >= 0)    //如果entries有值
            array[index++] = entries[index1].key;   //将entries数组的键逐个拷贝到数组中
        }
      }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值