C# ディクショナリ ソース コードの解析に関する注意事項

ディクショナリ: 一意のキーを使用して任意の値にアクセスできるキーと値のペアのコレクションです。これはハッシュ テーブルを使用して実装され、値の高速検索を可能にし、要素を追加、削除、および更新するためのメソッドを提供します。

ハッシュ テーブルの一般的な実装です。

公式ドキュメント:コレクション (C#) | Microsoft Learn

ソースアドレス: dictionary.cs (microsoft.com)

基本メンバー:

        //Entry结构体
        private struct Entry {
            public int hashCode;    // 哈希值,由key经过哈希函数计算出,映射到buckets数组的下标 -1代表未使用 
            public int next;        // 下一个元素结构体的索引,映射到entries数组的下标,-1代表其为当前hash链的最后一个元素,无法再往下查询
            public TKey key;           // 键 Key of entry
            public TValue value;         // 值 Value of entry
        }
 
        private int[] buckets; //桶数组,用来分散存储和快速查找,下标为key的哈希值对其长度取余
        private Entry[] entries; //Entry结构体数组,用来存储基本元素
        private int count;//当前已使用过的最长数量,值域为entries的长度
        private int version;//版本号,记录修改次数
        private int freeList;//空闲链表的第一位,对应entires的下标,0<freeList<count
        private int freeCount;//空闲链表的长度,0<freeCount<count
        private IEqualityComparer<TKey> comparer;//比较函数
        private KeyCollection keys;
        private ValueCollection values;

機能: 追加、削除、変更、チェック

初期化:初期化

        private void Initialize(int capacity) {
            int size = HashHelpers.GetPrime(capacity);//取不小于容量的质数
            buckets = new int[size];
            for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
            entries = new Entry[size];
            freeList = -1;
        }
// buckets = {-1,-1,-1 ...}
// entries = {e , e, e ...}
// e = {hashCode = 0,next = 0,key = null,value = null} //暂时可以这么理解

クリア: クリア

        public void Clear() {
            if (count > 0) {
                for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
                Array.Clear(entries, 0, count);
                freeList = -1;
                count = 0;
                freeCount = 0;
                version++;
            }
        }
//桶数组 buckets 全置为-1;
//元素数组 entries 清空
//闲置元素 = -1
//有效长度count  = 0 
//闲置数量freeCount = 0

増加: Add / Set (すべて最後に Insert メソッドに転送)

 public void Add(TKey key, TValue value) {
            Insert(key, value, true);
        }
public TValue this[TKey key] {
            get {
                int i = FindEntry(key);
                if (i >= 0) return entries[i].value;
                ThrowHelper.ThrowKeyNotFoundException();
                return default(TValue);
            }
            set {
                Insert(key, value, false);
            }
        }
        private void Insert(TKey key, TValue value, bool add) {
        
            if( key == null ) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            }
 
            if (buckets == null) Initialize(0);
            int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; //计算哈希码,最后会存在Entry元素里,此值和哈希函数相关,和数组长度/容量无关
            int targetBucket = hashCode % buckets.Length;//把哈希码映射到指定长度,得到的值会作为buckets数组的下标,值域也为(0,buckets.length)
 
            int collisionCount = 0;
 
//检查冲突/循环计数/长度计数
            for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) //巧妙的循环方法:尝试遍历当前key(算出的下标targetBucket)对应的entry链表的每一个元素
{
//如果当前key已存在,不允许add,允许修改值
                if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                    if (add) { 
                        ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
                    }
                    entries[i].value = value;
                    version++;
                    return;
                } 
 
                collisionCount++;//循环计数,相当于当前key指向的链表的长度,如果过长会触发容量的调整
            }

//如果当前key不存在,新加键值对(Entry)
            int index;  //index == entries数组的下标 == buckets数组存储的值
            if (freeCount > 0) {//如果当前有闲置的节点,择使用闲置节点(删除元素会产生空闲置点,详情可参考Remove方法)
                index = freeList; //freeList:闲置链表的表头,作为下标指向entries数组的某一个节点
                freeList = entries[index].next;//取下闲置链表的表头
                freeCount--;//更新闲置节点数
            }
            else {//没有闲置节点了,就往末尾添加元素
                if (count == entries.Length)//长度不够,扩容(详情见Resize方法)
                {
                    Resize();
                    targetBucket = hashCode % buckets.Length;//重新计算下标
                }
                index = count;
                count++;
            }
 //构建新的Entry元素
            entries[index].hashCode = hashCode;
            entries[index].next = buckets[targetBucket];//新元素插入链头第一步
            entries[index].key = key;
            entries[index].value = value;
            buckets[targetBucket] = index;//新元素插入链头第二步
            version++;
 

           //collisionCount 相关的一些扩容操作... 

 
        }

削除: 削除

        public bool Remove(TKey key) {
            if(key == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            }
 
            if (buckets != null) {
                int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
                int bucket = hashCode % buckets.Length;
                int last = -1;
                for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) {
                    if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                        if (last < 0) {
                            buckets[bucket] = entries[i].next;
                        }
                        else {
                            entries[last].next = entries[i].next;
                        }
                        entries[i].hashCode = -1;
                        entries[i].next = freeList;
                        entries[i].key = default(TKey);
                        entries[i].value = default(TValue);
                        freeList = i;
                        freeCount++;
                        version++;
                        return true;
                    }
                }
            }
            return false;
        }

チェック: TryGetValue

 public bool TryGetValue(TKey key, out TValue value) {
            int i = FindEntry(key);
            if (i >= 0) {
                value = entries[i].value;
                return true;
            }
            value = default(TValue);
            return false;
        }
 
 private int FindEntry(TKey key) {
            if( key == null) {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            }
 
            if (buckets != null) {
                int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
                for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
                    if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
                }
            }
            return -1;
        }

拡張: サイズ変更

       private void Resize(int newSize, bool forceNewHashCodes) {
            Contract.Assert(newSize >= entries.Length);
            int[] newBuckets = new int[newSize];
            for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1;
            Entry[] newEntries = new Entry[newSize];
            Array.Copy(entries, 0, newEntries, 0, count);
            if(forceNewHashCodes) {
                for (int i = 0; i < count; i++) {
                    if(newEntries[i].hashCode != -1) {
                        newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF);
                    }
                }
            }
            for (int i = 0; i < count; i++) {
                if (newEntries[i].hashCode >= 0) {
                    int bucket = newEntries[i].hashCode % newSize;
                    newEntries[i].next = newBuckets[bucket];
                    newBuckets[bucket] = i;
                }
            }
            buckets = newBuckets;
            entries = newEntries;
        }

おすすめ

転載: blog.csdn.net/weixin_40695640/article/details/130310643