我试图理解C#的通用词典的内部结构是如何工作的。我反编译并得到了字典代码。我重新编写了它,将键/值/哈希码略微分离为单独的数组(我省略了枚举器和接口实现,因为它们无关紧要)
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
public class Dictionary<TKey, TValue> {
int[] _Buckets;
int[] _HashCodes;
int[] _Next;
int _Count;
int _FreeList;
int _FreeCount;
TKey[] _Keys;
TValue[] _Values;
readonly IEqualityComparer<TKey> _Comparer;
public int Count {
get { return _Count - _FreeCount; }
}
public TValue this[TKey key] {
get {
int index = FindIndex(key);
if (index >= 0)
return _Values[index];
throw new KeyNotFoundException(key.ToString());
}
set { Insert(key, value, false); }
}
public Dictionary() : this(0, null) {
}
public Dictionary(int capacity) : this(capacity, null) {
}
public Dictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) {
}
public Dictionary(int capacity, IEqualityComparer<TKey> comparer) {
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity");
Initialize(capacity);
_Comparer = (comparer ?? EqualityComparer<TKey>.Default);
}
public Dictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null)
{
}
public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
: this((dictionary != null) ? dictionary.Count : 0, comparer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
foreach (KeyValuePair<TKey, TValue> current in dictionary)
Add(current.Key, current.Value);
}
public bool ContainsValue(TValue value) {
if (value == null) {
for (int i = 0; i < _Count; i++) {
if (_HashCodes[i] >= 0 && _Values[i] == null)
return true;
}
} else {
var defaultComparer = EqualityComparer<TValue>.Default;
for (int i = 0; i < _Count; i++) {
if (_HashCodes[i] >= 0 && defaultComparer.Equals(_Values[i], value))
return true;
}
}
return false;
}
public bool ContainsKey(TKey key) {
return FindIndex(key) >= 0;
}
public void Clear() {
if (_Count <= 0)
return;
for (int i = 0; i < _Buckets.Length; i++)
_Buckets[i] = -1;
Array.Clear(_Keys, 0, _Count);
Array.Clear(_Values, 0, _Count);
Array.Clear(_HashCodes, 0, _Count);
Array.Clear(_Next, 0, _Count);
_FreeList = -1;
_Count = 0;
_FreeCount = 0;
}
public void Add(TKey key, TValue value) {
Insert(key, value, true);
}
private void Resize(int newSize, bool forceNewHashCodes) {
int[] bucketsCopy = new int[newSize];
for (int i = 0; i < bucketsCopy.Length; i++)
bucketsCopy[i] = -1;
var keysCopy = new TKey[newSize];
var valuesCopy = new TValue[newSize];
var hashCodesCopy = new int[newSize];
var nextCopy = new int[newSize];
Array.Copy(_Values, 0, valuesCopy, 0, _Count);
Array.Copy(_Keys, 0, keysCopy, 0, _Count);
Array.Copy(_HashCodes, 0, hashCodesCopy, 0, _Count);
Array.Copy(_Next, 0, nextCopy, 0, _Count);
if (forceNewHashCodes) {
for (int i = 0; i < _Count; i++) {
if (hashCodesCopy[i] != -1)
hashCodesCopy[i] = (_Comparer.GetHashCode(keysCopy[i]) & 2147483647);
}
}
for (int i = 0; i < _Count; i++) {
int index = hashCodesCopy[i] % newSize;
nextCopy[i] = bucketsCopy[index];
bucketsCopy[index] = i;
}
_Buckets = bucketsCopy;
_Keys = keysCopy;
_Values = valuesCopy;
_HashCodes = hashCodesCopy;
_Next = nextCopy;
}
private void Resize()
{
Resize(PrimeHelper.ExpandPrime(_Count), false);
}
public bool Remove(TKey key)
{
if (key == null)
throw new ArgumentNullException("key");
int hash = _Comparer.GetHashCode(key) & 2147483647;
int index = hash % _Buckets.Length;
int num = -1;
for (int i = _Buckets[index]; i >= 0; i = _Next[i]) {
if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key)) {
if (num < 0)
_Buckets[index] = _Next[i];
else
_Next[num] = _Next[i];
_HashCodes[i] = -1;
_Next[i] = _FreeList;
_Keys[i] = default(TKey);
_Values[i] = default(TValue);
_FreeList = i;
_FreeCount++;
return true;
}
num = i;
}
return false;
}
private void Insert(TKey key, TValue value, bool add) {
if (key == null)
throw new ArgumentNullException("key");
if (_Buckets == null)
Initialize(0);
int hash = _Comparer.GetHashCode(key) & 2147483647;
int index = hash % _Buckets.Length;
for (int i = _Buckets[index]; i >= 0; i = _Next[i]) {
if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key)) {
if (add)
throw new ArgumentException("Key already exists: " + key);
_Values[i] = value;
return;
}
}
int num;
if (_FreeCount > 0) {
num = _FreeList;
_FreeList = _Next[num];
_FreeCount--;
} else {
if (_Count == _Keys.Length) {
Resize();
index = hash % _Buckets.Length;
}
num = _Count;
_Count++;
}
_HashCodes[num] = hash;
_Next[num] = _Buckets[index];
_Keys[num] = key;
_Values[num] = value;
_Buckets[index] = num;
}
private void Initialize(int capacity) {
int prime = PrimeHelper.GetPrime(capacity);
_Buckets = new int[prime];
for (int i = 0; i < _Buckets.Length; i++)
_Buckets[i] = -1;
_Keys = new TKey[prime];
_Values = new TValue[prime];
_HashCodes = new int[prime];
_Next = new int[prime];
_FreeList = -1;
}
private int FindIndex(TKey key) {
if (key == null)
throw new ArgumentNullException("key");
if (_Buckets != null) {
int hash = _Comparer.GetHashCode(key) & 2147483647;
for (int i = _Buckets[hash % _Buckets.Length]; i >= 0; i = _Next[i]) {
if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key))
return i;
}
}
return -1;
}
public bool TryGetValue(TKey key, out TValue value) {
int index = FindIndex(key);
if (index >= 0) {
value = _Values[index];
return true;
}
value = default(TValue);
return false;
}
public TValue ValueOrDefault(TKey key) {
return ValueOrDefault(key, default(TValue));
}
public TValue ValueOrDefault(TKey key, TValue defaultValue) {
//return this[key, defaultValue];
int index = FindIndex(key);
if (index >= 0)
return _Values[index];
return defaultValue;
}
private static class PrimeHelper {
public static readonly int[] Primes = new int[] {
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
};
public static bool IsPrime(int candidate) {
if ((candidate & 1) != 0) {
int num = (int)Math.Sqrt((double)candidate);
for (int i = 3; i <= num; i += 2) {
if (candidate % i == 0) {
return false;
}
}
return true;
}
return candidate == 2;
}
public static int GetPrime(int min) {
if (min < 0)
throw new ArgumentException("min < 0");
for (int i = 0; i < PrimeHelper.Primes.Length; i++) {
int prime = PrimeHelper.Primes[i];
if (prime >= min)
return prime;
}
for (int i = min | 1; i < 2147483647; i += 2) {
if (PrimeHelper.IsPrime(i) && (i - 1) % 101 != 0)
return i;
}
return min;
}
public static int ExpandPrime(int oldSize) {
int num = 2 * oldSize;
if (num > 2146435069 && 2146435069 > oldSize) {
return 2146435069;
}
return PrimeHelper.GetPrime(num);
}
}
}
令我困惑的是:
_FreeList
和_FreeCount
变量:它们代表什么?Count
和_Count
之间的区别是什么?_Next
似乎与哈希冲突有关,它是否存储具有相同哈希值的键/值的下一个索引?num
Remove
做了什么?Key
我们计算Index = GetHashCode(Key) % Buckets.Length;
,它为我们提供了一个可以在_Keys/_Values/_HashCodes
数组中使用的索引。为什么我们需要比较_HashCodes[i] == hash
?我的意思是我们通过计算哈希得到索引,所以当然_Keys[i]
哈希相同的哈希吧?请注意,有一个反编译代码并使用它的实际用例。 Unity3D不知道如何序列化字典,你通常的方法也不是很好,所以我采用了Dictionary的实际代码并用[SerializeField]
注释了必要的字段,现在Unity可以序列化这个类没有任何包装。
答案 0 :(得分:4)
您展示的实现使用数组_Keys
来存储密钥,使用_Values
来存储其对应的值。只有从索引零(包括零)到索引_Count
(独占)的这些数组的一部分被占用。 <{1}}及以上的元素未使用。
_Count
和_Keys
数组在添加时按顺序填充。发生删除时,相应的元素将放在空闲列表上。当发生下一次添加时,在考虑扩展到_Values
处的元素之前,将使用返回到空闲列表的最后一个元素。
_Count
似乎与哈希冲突有关,它是否存储具有相同哈希的键/值的下一个索引?
_Next
有两个目的,具体取决于是使用相应的项目还是在空闲列表中。
_Next
时,i
表示具有冲突哈希码的下一个元素的索引(它不一定是相同的哈希码!)_Next[i]
位于空闲列表中时,i
表示空闲列表中的下一个项目(即_Next[i]
之前放置在空闲列表中的项目 - 如果有)。
i
和Count
之间的区别是什么?
_Count
是_Count
和_Keys
数组的高水位线; _Values
是实际计数,计算为高水位数减去放置在空闲列表中的项目数(即最大增加减去当前删除的数量)。
因此,我们根据密钥计算
Count
,它为我们提供了一个可以在Index = GetHashCode(Key) % Buckets.Length;
/_Keys
/_Values
数组中使用的索引。为什么我们需要比较_HashCodes
?
因为_HashCodes[i] == hash
余数对于不同的哈希码值可能相同。您可以在调用GetHashCode(Key) % Buckets.Length
之前通过比较哈希代码来优化代码,尽管此步骤不是必需的。
Equals
num
做了什么?
我们要删除的项目可能是具有冲突哈希码的项目列表的一部分。该项目可能位于列表的开头,也可能位于列表中的其他位置。该代码使用Remove
以不同方式处理这些情况:
num
_Buckets[]
索引,而不是向前推广。 _Next[]
以负数开始,并在初始迭代后切换为非负数。检查next
表示&#34;我们是否在第一次迭代?&#34;