C#笔记啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
各数据类型的字节数
byte、sbyte、bool、char | 1 |
short、ushort | 2 |
int、uint、float | 4 |
long、ulong、double | 8 |
string(栈内存的引用地址) | 32位4,64位8 |
反射总结
得到Type
typeof(类名);
实例.GetType();
Type.GetType("命名空间.类名");
得到公共成员
MemberInfo memberInfos[]=type.GetMembers();
得到构造函数
得到所有:
ConstructorInfo[] constructorInfos=type.GetConstructors();
得到无参:
ConstructorInfo constructorInfo=type.GetConstructor(new Type[0]);
MyClass myClass=constructorInfo.Invoke(null) as MyClass;
得到有参:
ConstructorInfo constructorInfo2=type.GetConstructor(new Type[]{typeof(参数类型)});
myClass=constructorInfo2.Invoke(new object[]{2}) as MyClass;
Activator快速实例化
myClass=Activator.CreateInstance(type) as MyClass;
得到公共字段
按字段名得到:
FieldInfo fieldInfo=type.GetField(string 字段名);
得到所有字段
FieldInfo[] fieldInfos=type.GetFields();
公共字段读写
fieldInfo.GetValue(实例);
fieldInfo.SetValue(实例,数据);
得到公共方法
得到所有
MethodInfo[] methodInfos=type.GetMethods();
按方法名得到:
MethodInfo methodInfo=type.GetMethod(string 方法名,new Type[]{typeof(参数1类型),...});
执行公共方法
实例方法
methodInfo.Invoke(实例,new object[]{参数1,...});
静态方法
methodInfo.Invoke(null,new object[]{参数1,...});
获得List的Fields,什么都没得到
List<int>data=new List<int>(){123,14,4234};
Type dataType=data.GetType();
FieldInfo[] fieldInfos=dataType.GetFields();
正确用法:把List装到一个class里。
deepseek回答字典的底层机制
C# 的 Dictionary<TKey, TValue>
使用 链地址法(Chaining) 的改良实现来解决哈希冲突,这是一种基于数组索引的虚拟链表结构而非物理离散节点24。其核心设计融合了数组的连续存储优势和链表的冲突处理能力,具体实现如下:
一、底层数据结构与冲突解决机制
双数组结构
-
buckets[]
数组:存储桶的头节点索引(指向entries[]
的位置),初始值为-1
表示空桶45。 -
entries[]
数组:连续存储键值对(Entry
结构体),每个Entry
包含:struct Entry {public int hashCode; // 哈希码(低31位)public int next; // 冲突链的下一个节点索引(-1 表示结尾)public TKey key;public TValue value; }
next
字段形成逻辑链表,解决哈希冲突45。
-
冲突处理流程
当插入键值对时:计算键的哈希值
hashCode
,映射到buckets
数组的索引bucketIndex
45。若
buckets[bucketIndex]
为-1
,直接存入entries[]
的新位置。若发生冲突(
buckets[bucketIndex]
非-1
),将新Entry
的next
指向当前桶的头节点,并更新buckets[bucketIndex]
为新节点的索引67。
本质是通过next
字段在连续数组内模拟链表操作,而非动态分配离散节点46。
二、为何未采用开放地址法(Open Addressing)
内存与性能权衡
- 开放地址法要求所有数据存储在单一连续数组,扩容时需全量迁移,代价高昂810。
- 链地址法通过逻辑链表分离冲突数据,扩容仅需重建
buckets
索引,entries
数组可部分复用610。
删除操作高效性
- 开放地址法删除需标记“墓碑”位置,导致查找路径变长10。
- 链地址法直接修改
next
指针即可删除节点,无需特殊标记910。
高装载因子容忍度
- 当装载因子 >1 时(如数据量远超桶数量),链地址法仅延长链表长度,而开放地址法性能会急剧退化810。
- C#
Dictionary
默认装载因子为0.72
,超过时触发扩容56。
三、改良链地址法的优势
- 缓存友好性
entries[]
数组连续存储,相比物理链表减少内存碎片,提升 CPU 缓存命中率47。 - 预分配与复用
初始化时预分配entries[]
,删除节点时通过freeList
链表复用空位,减少内存分配开销67。
总结对比
特性 | C# 字典的链地址法改良 | 传统开放地址法 |
---|---|---|
冲突解决 | 数组内虚拟链表 (next 索引链) 47 | 线性/二次探测空槽 310 |
内存连续性 | 键值对在 entries[] 中连续存储 4 | 所有数据在单一数组连续存储 10 |
删除效率 | 直接修改索引链,高效 69 | 需标记墓碑,影响查找 10 |
高负载因子适应性 | 链表长度增加,性能线性下降 10 | 性能急剧退化 810 |
因此,C# Dictionary
通过数组化链地址法,在内存效率、冲突处理与动态扩容间取得了平衡4
追问:这么看来buckets数组是一个整数数组?
是的,C# 的 Dictionary<TKey, TValue>
中的 buckets
数组是一个整数数组(int[]
),其核心作用是通过存储索引值来关联哈希桶与键值对数据。具体实现细节如下:
一、buckets
数组的结构与功能
数据类型与用途
buckets
数组的每个元素是int
类型,存储的是entries
数组的索引值(即键值对在entries
中的位置)15。- 初始化时,
buckets
的所有元素默认值为-1
,表示空桶5。
哈希映射流程
- 当插入或查找键值对时,哈希函数计算键的哈希值,并通过取模运算映射到
buckets
的某个索引bucketIndex
46。 - 若
buckets[bucketIndex]
为-1
,表示无冲突,直接存入entries
;否则通过next
字段形成逻辑链表解决冲突15。
- 当插入或查找键值对时,哈希函数计算键的哈希值,并通过取模运算映射到
二、buckets
与 entries
的协同关系
entries
数组的存储entries
是Entry
结构体数组,包含hashCode
、next
、key
和value
字段5。next
字段指向同一哈希桶中的下一个Entry
索引(类似链表指针),形成冲突链16。
内存布局示例
textCopy Code
buckets = [ -1, 2, -1, 0 ] // 桶索引 3 指向 entries[0] entries = [ { hashCode=123, next=1, key="K1", value="V1" }, // 冲突链头 { hashCode=123, next=-1, key="K2", value="V2" } // 冲突链尾 ]
三、设计优势与性能考量
- 高效的内存利用
- 整数数组
buckets
占用空间小,且通过entries
的连续存储减少内存碎片45。
- 整数数组
- 快速冲突解决
- 逻辑链表(通过
next
索引)比物理链表(如LinkedList
)更缓存友好,减少指针跳转开销14。
- 逻辑链表(通过
总结
buckets
是int[]
类型,核心功能是通过索引关联哈希桶与entries
中的键值对15。- 这种设计结合了数组的连续性和链表的冲突处理能力,兼顾了哈希表的高效查找与动态扩展需求
人话总结
字典有2个数组:整数数组buckets,结构体数组entries,哈希冲突的键会得到同一个bucketsIndex,buckets的值是这些哈希冲突的键值对中的第一个在entries的索引,通过entry结构体的next找到下一个键值对索引,直到找到next==-1的也就是最后一个键值对,把新键值对加入entries最后,next==-1的键值对next改成新加入的键值对索引。
如果某个bucketsIndex下的值是-1,则直接加入entries最后,buckets[bucketsIndex]改成这个新加的索引。
键值对在内存是连续、乱序的。