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