dotNet源碼解讀--HashTable目錄擴充的奧秘

來源:互聯網
上載者:User

標籤:style   blog   class   c   code   ext   

dotNet源碼解讀--HashTable目錄擴充的奧秘



摘要:為了探索dotnet中hashtable的目錄結構及與目錄擴充相關的演算法,本文通過對相關源碼的閱讀與分析,得出如下結論,hashtable的目錄是由數組組織,目錄元素代表一個資料節點,不是資料桶。目錄擴充是擴充目前的目錄長度2倍往1遍曆過程中遇到的第一個素數。目錄擴充觸發條件:裝載因子式的觸發,同時考慮到“雜亂程度”需要進行重新散列。目錄擴充時需要遍曆原有目錄中所有的元素。查詢過程與探測再散列類似。

關鍵詞:dotnet,hashmap,目錄擴充方法,目錄擴充觸發條件

一、目錄結構

本文源自:http://blog.csdn.net/daliaojie/article/details/26366795

首先我們看一下該類主要的成員變數

        private bucket[] buckets;        private int count;        private const int InitialSize = 3;        private float loadFactor;        private int loadsize;        private int occupancy;   

buckets為目錄,使用數組維繫的。

再看bucket是什麼:

        private struct bucket        {            public object key;            public object val;            public int hash_coll;        }

原來他是一個結構體,值類型。也就是說,hashtable中的目錄位置並不是一個資料桶,而是一個鍵值對。僅僅一個資料節點。


count就是裡面已經裝載了多少個元素,loadfactor和loadsize分別是裝載因子與閥值。occupancy待會兒說。


二、插入操作

1、散列衝突解決方式

 public virtual void Add(object key, object value)        {            this.Insert(key, value, true);        }
我們追蹤insert方法:

 private void Insert(object key, object nvalue, bool add)        {            uint num;            uint num2;            if (key == null)            {                throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));            }            if (this.count >= this.loadsize)            {                this.expand();            }            else if ((this.occupancy > this.loadsize) && (this.count > 100))            {                this.rehash();            }            uint num3 = this.InitHash(key, this.buckets.Length, out num, out num2);            int num4 = 0;            int index = -1;            int num6 = (int) (num % this.buckets.Length);        Label_0071:            if (((index == -1) && (this.buckets[num6].key == this.buckets)) && (this.buckets[num6].hash_coll < 0))            {                index = num6;            }            if ((this.buckets[num6].key == null) || ((this.buckets[num6].key == this.buckets) && ((this.buckets[num6].hash_coll & 0x80000000L) == 0L)))            {                if (index != -1)                {                    num6 = index;                }                Thread.BeginCriticalRegion();                this.isWriterInProgress = true;                this.buckets[num6].val = nvalue;                this.buckets[num6].key = key;                this.buckets[num6].hash_coll |= (int) num3;                this.count++;                this.UpdateVersion();                this.isWriterInProgress = false;                Thread.EndCriticalRegion();            }            else if (((this.buckets[num6].hash_coll & 0x7fffffff) == num3) && this.KeyEquals(this.buckets[num6].key, key))            {                if (add)                {                    throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", new object[] { this.buckets[num6].key, key }));                }                Thread.BeginCriticalRegion();                this.isWriterInProgress = true;                this.buckets[num6].val = nvalue;                this.UpdateVersion();                this.isWriterInProgress = false;                Thread.EndCriticalRegion();            }            else            {                if ((index == -1) && (this.buckets[num6].hash_coll >= 0))                {                    this.buckets[num6].hash_coll |= -2147483648;                    this.occupancy++;                }                num6 = (int) ((num6 + num2) % ((ulong) this.buckets.Length));                if (++num4 < this.buckets.Length)                {                    goto Label_0071;                }                if (index == -1)                {                    throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_HashInsertFailed"));                }                Thread.BeginCriticalRegion();                this.isWriterInProgress = true;                this.buckets[index].val = nvalue;                this.buckets[index].key = key;                this.buckets[index].hash_coll |= (int) num3;                this.count++;                this.UpdateVersion();                this.isWriterInProgress = false;                Thread.EndCriticalRegion();            }        }

插入操作的剛開始,就是判空與判斷是否需要擴充和重新散列。擴充(expand)與重新散列(rehash)兩個操作的觸發條件及操作,我們稍後追蹤。

也就是說我們假設目前不需要擴充與重新散列。

插入操作稍後計算了key所對應的目錄位置index,如果該位置無資料即可以佔用,如果已經被佔用,並且key值相等,則預設操作是替換value的值。否則,該目錄位置已經

被佔用,並且key不相等,那麼我們會再選另一個位置來檢測是否合適,如果合適,就插入。再次選擇位置的方式,不是簡單的選取隔壁的位置,而是加上一個數。這樣做是為了更快的找到空閑位置,很明顯,hash解決衝突的方式的開放地址法。

這裡的

occupancy

應該理解為,有多少個元素不在他該在的位置,及key計算出的index不是其所子啊的位置。

2、再散列與擴充方法

再散列觸發條件

(this.occupancy > this.loadsize) && (this.count > 100)


也即是說,如果佔據錯了位置的元素達到這個閥值並且成員裝載數目達到100時,才啟動再散列

擴充操作的觸發條件:

this.count >= this.loadsize

也就是說,負載元素數目達到這個閥值時觸發擴充操作,其實還是和負載因子有關係。


我們看他們對應的源碼

 private void rehash()        {            this.rehash(this.buckets.Length);        }

 private void expand()        {            int prime = HashHelpers.GetPrime(this.buckets.Length * 2);            this.rehash(prime);        }

我們發現他們都是調用的

 private void rehash(int newsize)        {            this.occupancy = 0;            Hashtable.bucket[] newBuckets = new Hashtable.bucket[newsize];            for (int i = 0; i < this.buckets.Length; i++)            {                Hashtable.bucket bucket = this.buckets[i];                if ((bucket.key != null) && (bucket.key != this.buckets))                {                    this.putEntry(newBuckets, bucket.key, bucket.val, bucket.hash_coll & 0x7fffffff);                }            }            Thread.BeginCriticalRegion();            this.isWriterInProgress = true;            this.buckets = newBuckets;            this.loadsize = (int) (this.loadFactor * newsize);            this.UpdateVersion();            this.isWriterInProgress = false;            Thread.EndCriticalRegion();        }

只是,在散列操作傳入的是目前的目錄的長度,而擴充傳入的是,從目前的目錄長度的2倍往2遍曆遇到的第一個素數。他們認為素數散列後衝突機率小。

這裡求素數時的策略,參考文章:

我們看這個方法:

將站錯位置的元素數目置零。

new一個指定長度的bucket數組。

在老的bucket數組目錄中遍曆每一個存在的元素。

將他們放到新的目錄中。

  private void putEntry(bucket[] newBuckets, object key, object nvalue, int hashcode)        {            uint num = (uint) hashcode;            uint num2 = (uint) (1 + (((num >> 5) + 1) % (newBuckets.Length - 1)));            int index = (int) (num % newBuckets.Length);        Label_0017:            if ((newBuckets[index].key == null) || (newBuckets[index].key == this.buckets))            {                newBuckets[index].val = nvalue;                newBuckets[index].key = key;                newBuckets[index].hash_coll |= hashcode;            }            else            {                if (newBuckets[index].hash_coll >= 0)                {                    newBuckets[index].hash_coll |= -2147483648;                    this.occupancy++;                }                index = (int) ((index + num2) % ((ulong) newBuckets.Length));                goto Label_0017;            }        }

這個操作和插入有些類似,都要做衝突解決的方法。

這裡我們知道,目錄擴充的方法,是擴充小於2倍目前的目錄長度的第一個素數的目錄。

結尾:

通過本次對dotNet中hashmap源碼的分析與解讀,得出如下結論,hashtable的目錄是由數組組織,目錄元素代表一個資料節點,不是資料桶。目錄擴充是擴充目前的目錄長度2倍往1遍曆過程中遇到的第一個素數。目錄擴充觸發條件:裝載因子式的觸發,同時考慮到“雜亂程度”需要進行重新散列。目錄擴充時需要遍曆原有目錄中所有的元素。查詢過程與探測再散列類似。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.