通過.net core原始碼看下Dictionary的實現
.net core的程式碼位置
https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs
C#
中,Dictionary
這個資料結構並不是很容易理解,因為看上不去並不像C++
的map
。底層是如何實現一個字典的並完全可知,因為從資料結構來說,很多結構都可以支援一個類似的加速key-value
對儲存的訪問形式。比如tree
,跳錶,hashtable
等等。
基於bucket
的Hashtable
Dictionary
的基本思想是通過一個Entry
數值儲存資料(key
和value
),其中的資料是緊密排布的。然後,通過bucket
陣列實現hashcode
加速查詢。如果兩個物件的hashcode%length
(數值的長度)相等,實現類似hashtable
碰撞的退避規則,並通過Entry.next
的引用住新的退避位置(用陣列下標實現連線)。
private struct Entry
{
public int hashCode; // Lower 31 bits of hash code, -1 if unused
public int next; // Index of next entry, -1 if last
public TKey key; // Key of entry
public TValue value; // Value of entry
}
private int[] _buckets;
private Entry[] _entries;
一個key-value
資料,在經過Key.GetHashCode
後的返回值,再對_buckets
的長度取模。決定隱射到的_buckets
下標,而實際儲存的區域_entries
是一個連續儲存的陣列,用來儲存鍵值對(Entry
)。如上圖,如果插入時出現hash
桶碰撞,會直接找到下一個空的格子插入資料,並把這個格子的id
儲存到上一個entry.next
中,方便刪除或查詢時使用。
反之,如果刪除資料時,就需要級聯更新entry.next
的情況。刪除的關鍵程式碼如下,如果是一個通過next
找到的entry
,那last
必然>0
,所以需要把last.next
指向自己的next
,繞過自己。如果last<0
則說明,自己是第一個元素,直接更新bucket
指向自己的next
(可能是-1
,也可能是真的下一個元素的下標)。
if (last < 0)
{
// Value in buckets is 1-based
buckets[bucket] = entry.next + 1;
}
else
{
entries[last].next = entry.next;
}
關於Keys
和Values
private KeyCollection _keys;
private ValueCollection _values;
許多時候,我們會用到對Keys
和Values
的訪問。那我們來看看,這兩個屬性是如何實現的。先看一下KeyCollection
的實現。這裡刪除了一些多餘的程式碼,可以看出,他僅僅對dict
的一個組合關係,內部的實際工作者是dict
。
public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey>
{
private Dictionary<TKey, TValue> _dictionary;
public KeyCollection(Dictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
void ICollection<TKey>.Add(TKey item)
=> ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
void ICollection<TKey>.Clear()
=> ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
bool ICollection<TKey>.Contains(TKey item)
=> _dictionary.ContainsKey(item);
}
然後,看一下迭代過程的實現。非常簡單,僅僅是每次都把_currentKey
賦值為_entries
的下一個元素。所以,可以看出來,Keys
的訪問是有序的(按插入順序)。
public bool MoveNext()
{
while ((uint)_index < (uint)_dictionary._count)
{
ref Entry entry = ref _dictionary._entries[_index++];
if (entry.hashCode >= 0)
{
_currentKey = entry.key;
return true;
}
}
_index = _dictionary._count + 1;
_currentKey = default;
return false;
}
values
和keys
的實現是完全一致的,所以Values
的訪問和Keys
的訪問效能是差不多的,不存在訪問Keys
快,訪問Values
慢的情況。
關於空間大小演算法
大家知道hash
表是需要先分配一塊比較大的空間,並在保持一定資料密度的情況下,會擁有比較高的儲存和訪問效率。
C#
的dict
,永遠會去找當前需求的capacity
的下一個素數,作為陣列的分配size
。如果,預設new Dict
,傳遞的capacity
是0
,那麼實際此時的_entries
大小是3
。
找素數的邏輯稍微提下。會先順序遍歷儲存的primes
陣列;如果找不到,再用逐個數字遍歷的方式找接下來的素數。
public static readonly int[] primes = {
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 };
關於讀取資料的效率
題外話,講一下有的同學喜歡這麼寫資料訪問的程式碼。
if (techAddonDict.ContainsKey(3))
{
var c = techAddonDict[3];
}
從底層來說,所有查詢的程式碼,都會先通過bucket
找到一次entry
物件(通過FindEntry
函式)。那麼上一段函式中實際需要訪問兩次FindEntry
函式。
float v;
if (techAddonDict.TryGetValue(3, out v))
{
//todo xxx
}
這段函式就很明顯了,只需要訪問一次FindEntry
函式,效能自然會好一倍。
相關文章
- .net原始碼分析 – Dictionary泛型原始碼泛型
- asp.net core 實現 face recognition 使用 tensorflowjs(原始碼)ASP.NETJS原始碼
- .net core 原始碼分析原始碼
- 透過.NET Core+Vue3 實現SignalR即時通訊功能VueSignalR
- ASP.NET Core 通過 Microsoft.DotNet.Watcher.Tools 實現熱部署ASP.NETROS熱部署
- .NET Core中JWT+Auth2.0實現SSO,附完整原始碼(.NET6)JWT原始碼
- .NET Core HttpClient原始碼探究HTTPclient原始碼
- .Net Core Configuration原始碼探究原始碼
- .NET Core Session原始碼探究Session原始碼
- 原始碼解析.Net中Middleware的實現原始碼
- 原始碼解析.Net中DependencyInjection的實現原始碼
- 【nodejs原理&原始碼賞析(5)】net模組與通訊的實現NodeJS原始碼
- ASP.NET Core+Vue3 實現SignalR通訊ASP.NETVueSignalR
- 【SignalR全套系列】之在.Net Core 中實現SignalR實時通訊SignalR
- 原始碼解析.Net中IConfiguration配置的實現原始碼
- .Net Core中的配置檔案原始碼解析原始碼
- asp.net core原始碼解讀ASP.NET原始碼
- 淺談.Net Core DependencyInjection原始碼探究原始碼
- .NET Core 3.0 之初識Host原始碼原始碼
- 使用.Net Core實現的一個圖形驗證碼
- .Net Core快取元件(MemoryCache)原始碼解析快取元件原始碼
- ASP.NET Core[原始碼分析篇] - StartupASP.NET原始碼
- .NET Core 3.0之Configuration原始碼探究(一)原始碼
- .Net Core Logging模組原始碼閱讀原始碼
- org.reflections 介面通過反射獲取實現類原始碼研究反射原始碼
- .NET Core CSharp 中級篇 2-2 List,ArrayList和DictionaryCSharp
- 通過原始碼分析Mybatis的功能原始碼MyBatis
- 30分鐘通過Kong實現.NET閘道器
- CoreFX中Dictionary<TKey, TValue>的原始碼解讀原始碼
- 從Dictionary原始碼看雜湊表原始碼
- .NET Core 3.0之深入原始碼理解HttpClientFactory(二)原始碼HTTPclient
- .NET Core 3.0之深入原始碼理解HttpClientFactory(一)原始碼HTTPclient
- ASP.NET Core[原始碼分析篇] - 認證ASP.NET原始碼
- ASP.NET CORE 入門教程(附原始碼)ASP.NET原始碼
- .NET Core 3.0之深入原始碼理解Configuration(三)原始碼
- .NET Core 3.0之深入原始碼理解Configuration(二)原始碼
- .Net Core實現基於Quart.Net的任務管理
- .net core下訪問控制層的實現