通过.net core源码看下Dictionary的实现
.net core的代码位置
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;
if (last < ) { // Value in buckets is 1-based buckets[bucket] = entry.next + ; } 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 >= ) { _currentKey = entry.key; return true; } } _index = _dictionary._count + ; _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 = { , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , };
关于读取数据的效率
题外话,讲一下有的同学喜欢这么写数据访问的代码。
if (techAddonDict.ContainsKey()) { var c = techAddonDict[]; }
从底层来说,所有查找的代码,都会先通过bucket找到一次entry对象(通过FindEntry函数)。那么上一段函数中实际需要访问两次FindEntry函数。
float v;
//todo xxx
}
这段函数就很明显了,只需要访问一次FindEntry函数,性能自然会好一倍。
通过.net core源码看下Dictionary的实现的更多相关文章
- 一起来看CORE源码(一) ConcurrentDictionary
先贴源码地址 https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Col ...
- ASP.NET Core[源码分析篇] - Authentication认证
原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...
- 从源码看JDK提供的线程池(ThreadPoolExecutor)
一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...
- 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器
1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...
- 源码分析之Dictionary笔记
接下来我们一步步来熟悉 Dictionary的底层结构实现,下面的MyDictionary等同于源码中的Dictionary看待. 首先我们定义一个类 MyDictionary,类中定义一个结构Ent ...
- 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计
使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...
- DOTNET CORE源码分析之IOC容器结果获取内容补充
补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...
- 从Linux源码看Socket(TCP)的listen及连接队列
从Linux源码看Socket(TCP)的listen及连接队列 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看 ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
随机推荐
- 用bat写的一个小病毒
最近看了一点bat的知识,具体说是看了一个博客:http://blog.csdn.net/qsyzb/article/details/17364581 用了三天才看完=.=,感觉作者整理整理可以把博客 ...
- ruby 变量和方法
def say_goodnight(name) result ="Good night ." +name return result end def say_goodmorning ...
- python的argparse模块
一.简介: argparse是python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块.argparse模块的作用是用于解析命令行参数,例如python parseTes ...
- android json解析(JSONObject方法实现)
今天刚刚学到json解析,看了一整天,大概了解到json就是你通过一个API(我用的聚合数据的API)发送一个请求,接着会收到json数据,比如说天气预报吧,他会给你发送一大段字符串,大概是未来几天的 ...
- 612D The Union of k-Segments
传送门 题目大意 给定n个区间,问你被覆盖至少k次的区间(两端连续区间可以合并)最少有多少个,并输出. 分析 大水题呀QwQ,只需要将每个点的位置及它是左端点还是右端点这两个信息存起来然后进行一些简单 ...
- CF1030F Putting Boxes Together
昨晚的比赛题.(像我这种蒟蒻只能打打div2) 题意 给你$n$个物品,每一个物品$i$,有一个权值$w_i$和一个位置$a_i$,定义移动一个物品$i$到位置$t$的代价为$w_i * \left ...
- Altium Designer 3D模型的下载与添加
先 先晒几个图:是不是很逼真啊.. ---------------------------------------教程---------------------------------------- ...
- python(一):作用域
与c相比,python作用域很奇特. 在Python中变量的作用域是由它在源代码中的位置决定的,这一点与c相似. python只支持4种作用域,即局部作用域,全局作用域,内置作用域,嵌套作用域. 比较 ...
- Core中间件——访问记录
引言 上半年使用的thinkjs开发的node项目有一个优点就是所有请求都会有日志记录在控制台输出,里面包含了请求地址以及耗时.我就希望在.net中也可以实现这样子的功能,正好想到了中间件,所以就用中 ...
- Git解决pull无法操作成功
https://blog.csdn.net/chenjunfengf/article/details/78301957 场景 在git pull的时候,如果本地代码有改动,而服务器上代码也已经被其他人 ...