.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 < )

        {

            // Value in buckets is 1-based

            buckets[bucket] = entry.next + ;

        }

        else

        {

            entries[last].next = entry.next;

        }
  •         

关于Keys和Values

  1.  
    private KeyCollection _keys;
  2.  
    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;

if (techAddonDict.TryGetValue(3, out v))
{

//todo xxx

}

这段函数就很明显了,只需要访问一次FindEntry函数,性能自然会好一倍。

通过.net core源码看下Dictionary的实现的更多相关文章

  1. 一起来看CORE源码(一) ConcurrentDictionary

    先贴源码地址 https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Col ...

  2. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  3. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  4. 一个由正则表达式引发的血案 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. 一些特殊字符,如“&”,“- ...

  5. 源码分析之Dictionary笔记

    接下来我们一步步来熟悉 Dictionary的底层结构实现,下面的MyDictionary等同于源码中的Dictionary看待. 首先我们定义一个类 MyDictionary,类中定义一个结构Ent ...

  6. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  7. DOTNET CORE源码分析之IOC容器结果获取内容补充

    补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...

  8. 从Linux源码看Socket(TCP)的listen及连接队列

    从Linux源码看Socket(TCP)的listen及连接队列 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看 ...

  9. 从源码看Azkaban作业流下发过程

    上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...

随机推荐

  1. POJ 1151 扫描线 线段树

    题意:给定平面直角坐标系中的N个矩形,求它们的面积并. 题解:建立一个四元组(x,y1,y2,k).(假设y1<y2)用来储存每一条线,将每一条线按x坐标排序.记录所有的y坐标以后排序离散化.离 ...

  2. 阿里云 linux搭建git服务器

    git是非常方便的版本控制工具,目前网上有很多免费的git仓库可以给我们使用,但是有些时候我们并不放心将我们的项目寄放在别人的服务器上,这个时候就需要自己搭建一个git服务器. 在linux上面搭建g ...

  3. Verilog 语言 001 --- 入门级 --- 编写一个半加器电路模块

    Verilog 语言编写一个 半加器 电路模块 半加器 的电路结构: S = A 异或 B C = A 与 B 1. 程序代码 module h_adder (A, B, SO, CO); input ...

  4. window.location和window.location.href和document.location的关系

    1,首先来区分window.location和window.location.href. window.location.href是一个字符串. 而window.location是一个对象,包含属性有 ...

  5. [译]Javascript 参数(arguments)对象

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  6. adb 无法调试的问题,ADB server didn't ACK,* failed to start daemon *

    The connection to adb is down, and a severe error has occured. You must restart adb and Eclipse. Ple ...

  7. txt中把换行替换为空格

    把合适改为html后打开,换行都没了,然后复制到另一个txt即可

  8. Git入门操作

    仅学习Git的一些入门操作比较容易,平时更多地使用GitHub,不过今天我想自个搭个服务练练手.当看完一些材料合作了一些验证之后,才发现其实所谓的服务和之前的svn完全不一样了.过程记录如下: Lin ...

  9. css实现点点点效果

    @keyframes dotDotDoting{ 0% { width 0px margin-right 15px } 25% { width 0px margin-right 15px } 50% ...

  10. Linux配置国内的Yum源

    因为Linux默认的yum源是国外的源,所以会有卡顿,缓慢的情况.而国内的Yum源相对速度较快,现在也比较成熟,所以给Linux更换国内Yum源是一个很好的选择. 1.  备份(备份之前需要yum i ...