日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

浅谈C#中Dictionary的实现。

發布時間:2023/12/19 综合教程 30 生活家
生活随笔 收集整理的這篇文章主要介紹了 浅谈C#中Dictionary的实现。 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

Dictionary在C#中算是比較常用的數據結構了,它的讀取速度很快,效率很高。以前沒有怎么看它的源碼實現,前幾天看了看它的源碼實現,還是有點意思的,下面我將逐步說下它的實現原理。

數據結構

它是通過Hash Bucket和鏈表形成的數據結構,將一份數據分為多個鏈表,且每個鏈表都對應它的Bucket。可以看以下的圖:

看不明白不要急,我們先看源碼Dictionary類里面定義的字段都有什么。

private struct Entry {
    public int hashCode;    // 每個K/V對應的Hash值
    public int next;        // 指向下一個K/V的index,-1代表最后一個
    public TKey key;           // key的值
    public TValue value;         // Value的值
}

private int[] buckets; //定義桶的數組
private Entry[] entries; //定義元素的數組
private int count; //元素總數量
private int version; //版本
private int freeList; //被移除后,空閑元素的下標
private int freeCount; //空閑元素的數量

有了上面字段的定義,接下來我分分析它是怎么添加元素,尋找元素和刪除元素的。

添加元素

首先我們先把主要的源碼貼出來,從源碼上分析主要實現。

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;
    int targetBucket = hashCode % buckets.Length; //獲取對應的目標Bucket

    //尋找是否有相同key的元素
    for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
        if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
            if (add) { 
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
            }
            entries[i].value = value;
            version++;
            return;
        } 

    }
    int index;
    if (freeCount > 0) { //判斷現在是否有空閑的元素,優先使用空閑的元素
        index = freeList;
        freeList = entries[index].next;
        freeCount--;
    }
    else {  
        if (count == entries.Length) //判斷是否存儲的項和Entries的長度,相等的話,就重新擴容。
        {
            Resize();// 擴容Buctet和Entries的大小
            targetBucket = hashCode % buckets.Length;
        }
        index = count;
        count++;
    }

    entries[index].hashCode = hashCode;
    entries[index].next = buckets[targetBucket];
    entries[index].key = key;
    entries[index].value = value;
    buckets[targetBucket] = index;
    version++;
}

源碼詳細講解。

初次添加元素時,如果構造函數中不傳入大小,默認會自動取一個最小的質數(即:3)來作為桶的大小和元素集合的大小,并且初始換里面的字段變量。
在尋找元素插入的位置的時候,首先通過元素的hashcode % bucket的長度優先得到要插入的目標的Bucket是哪個,然后將元素的hashCode和next賦值。
這里next賦值的話詳細說一下。首先,我們先分析buckets,它里面的每個bucket保存的是對應鏈表最后一個元素的下標,可以通過最后一行代碼得知,每次給元素賦值之后,當前元素的下標,會賦值給對應的bucket。
而每個新插入元素,只需要當前bucket里面的值賦值給它當前的next指向的index就可以了。

圖示

查找元素

我們還是先簡單看一下主要實現部分的源碼。

private int FindEntry(TKey key) {
    if( key == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

    if (buckets != null) {
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; //首先獲取key的hashCode
        //尋址的第一個元素就是對應目標桶里面記錄的index,然后通過對應元素的next指向下一個元素,當next為-1時,就是代表已經到最后一個元素了。
        for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
            //判斷元素的hashcode和key是否都相等。
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
        }
    }
    return -1;
}

源碼詳細講解。

其實這里代碼已經寫的很清楚了,它是先找到目標桶,因為目標桶里面記錄的是它對應鏈表的最后一個元素的下標,然后順著元素的next找,直到找到這個元素為止。
可以仔細想想,這樣的話,每次查找就可以過濾一大批的數據,所以查的速度就更快了,但是當數據量大的時候,也是會有效率問題。

圖示

移除元素

還是先簡單看下源代碼主要實現的部分。

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; //把buctet指向的下標,指向下一個元素
                }
                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++; //每次移除,空閑的元素+1
                version++;
                return true;
            }
        }
    }
    return false;
}

源碼詳細講解。

查找對應的元素和上面的邏輯其實是一樣的,它這里定義了一個變量,用來記錄上一個元素的下標。
當找到對應的元素時,把上個元素的next指向當前被移除元素的next,即把當前被移除的元素跳過去。
然后將被移除元素的字段初始化,需要注意的是這個next的值,它用的是freelist,記錄最后一個被移除元素的index,每移除一個元素,被移除的元素數量就+1,即freecount。
被移除的元素也會形成一個鏈表,它的next首部元素next指向-1,后邊被移除的元素next指向上一個被移除元素的index。
回過頭再去看添加的時候,它會判斷,freeCount的數量是否是大于0的,如果大于0的話,優先使用被移除元素的位置并填充它們,它的index就是freeList,然后再把當前元素的next賦值給
freelist(即下次再插入元素的時候,就是上一個被移除元素的下標)。
最后在下面給當前元素賦值的時候,它的next又指向當前bucket里面的值,即作為對應鏈表的尾部。

圖示

關于擴容

先簡單看下主要實現部分的源碼。

private void Resize(int newSize, bool forceNewHashCodes) {
    Contract.Assert(newSize >= entries.Length); // 這個newSize是獲取大于count的最小質數
    int[] newBuckets = new int[newSize];
    for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; //初始化每個bucket的值
    Entry[] newEntries = new Entry[newSize];
    Array.Copy(entries, 0, newEntries, 0, count); //將原來的entries的值copy到新的entries里面

    for (int i = 0; i < count; i++) {
        if (newEntries[i].hashCode >= 0) { //判斷hashcode代表是有效的entry
            int bucket = newEntries[i].hashCode % newSize; 
            newEntries[i].next = newBuckets[bucket];
            newBuckets[bucket] = i; //上面的操作就是重新找新的桶,然后重新給entey的next賦值。
        }
    }
    buckets = newBuckets;
    entries = newEntries;
}

源碼詳細講解

這里的newSize會在里面的元素數達到entries的長度是擴容,它是在一個helper類里面進行取值,它是拿大于它的最小質數。
這個新的size就是buckets的長度和entries的長度,先把原來的entries的值copy到新的entries里面。
然后循環新的的entries,重新定義新的桶的值并且給原來的數據,重新形成新的鏈表。

總結

關于dictionary的解析也是我一個簡單的理解吧,主要還是看源碼能了解的更多,希望能夠幫助到需要的人。
微軟源碼的地址可以看這個: https://referencesource.microsoft.com/

總結

以上是生活随笔為你收集整理的浅谈C#中Dictionary的实现。的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。