├── .gitattributes ├── .gitignore ├── ConcurrentDictionaryMini.md ├── DictionaryMini.md ├── DictionaryMini ├── DictionaryMini.sln ├── DictionaryMini │ ├── ConcurrentDictionaryMini.cs │ ├── DictionaryMini.cs │ ├── DictionaryMini.csproj │ └── HashHelpersMini.cs └── Example │ ├── App.config │ ├── Example.csproj │ ├── Program.cs │ └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── Pic ├── ConsurrentDictionary.png ├── HashFunction.svg ├── chain.gif ├── hashtable0.svg ├── hashtable1.svg ├── hashtable2.svg └── hashtable3.svg └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_h.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *_wpftmp.csproj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush personal settings 296 | .cr/personal 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Local History for Visual Studio 334 | .localhistory/ 335 | -------------------------------------------------------------------------------- /ConcurrentDictionaryMini.md: -------------------------------------------------------------------------------- 1 | # 背景 2 | 3 | 在上一篇文章[你真的了解字典吗?](https://www.cnblogs.com/CoderAyu/p/10360608.html)一文中我介绍了Hash Function和字典的工作的基本原理. 4 | 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. 5 | 6 | 查阅相关资料后,发现字典.net中Dictionary本身时不支持线程安全的,如果要想使用支持线程安全的字典,那么我们就要使用ConcurrentDictionary了. 7 | 在研究ConcurrentDictionary的源码后,我觉得在ConcurrentDictionary的线程安全的解决思路很有意思,其对线程安全的处理对对我们项目中的其他高并发场景也有一定的参考价值,在这里再次分享我的一些学习心得和体会,希望对大家有所帮助. 8 | 9 | # Concurrent 10 | 11 | ConcurrentDictionary是Dictionary的线程安全版本,位于System.Collections.Concurrent的命名空间下,该命名空间下除了有ConcurrentDictionary,还有以下Class都是我们常用的那些类库的线程安全版本. 12 | 13 | [BlockingCollection](https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.blockingcollection-1?view=netframework-4.7.2):为实现 [IProducerConsumerCollection](https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.iproducerconsumercollection-1?view=netframework-4.7.2) 的线程安全集合提供阻塞和限制功能。 14 | 15 | [ConcurrentBag](https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentbag-1?view=netframework-4.7.2):表示对象的线程安全的无序集合. 16 | 17 | [ConcurrentQueue](https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=netframework-4.7.2):表示线程安全的先进先出 (FIFO) 集合。 18 | 19 | 如果读过我上一篇文章[你真的了解字典吗?](https://www.cnblogs.com/CoderAyu/p/10360608.html)的小伙伴,对这个`ConcurrentDictionary`的工作原理应该也不难理解,它是简简单单地在读写方法加个`lock`吗? 20 | 21 | # 工作原理 22 | 23 | ## Dictionary 24 | 25 | 如下图所示,在字典中,数组entries用来存储数据,buckets作为桥梁,每次通过hash function获取了key的哈希值后,对这个哈希值进行取余,即`hashResult%bucketsLength=bucketIndex`,余数作为buckets的index,而buckets的value就是这个key对应的entry所在entries中的索引,所以最终我们就可以通过这个索引在entries中拿到我们想要的数据,整个过程不需要对所有数据进行遍历,的时间复杂度为1. 26 | 27 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/hashtable1.svg?sanitize=true) 28 | 29 | ## ConcurrentDictionary 30 | 31 | ConcurrentDictionary的数据存储类似,只是buckets有个更多的职责,它除了有dictionary中的buckets的桥梁的作用外,负责了数据存储. 32 | 33 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/ConsurrentDictionary.png?sanitize=true) 34 | 35 | key的哈希值与buckets的length取余后`hashResult%bucketsLength=bucketIndex`,余数作为buckets的索引就能找到我们要的数据所存储的块,当出现两个key指向同一个块时,即上图中的John Smith和Sandra Dee他同时指向152怎么办呢?存储节点Node具有Next属性执行下个Node,上图中,node 152的Next为154,即我们从152开始找Sandra Dee,发现不是我们想要的,再到154找,即可取到所需数据. 36 | 37 | 由于官方原版的源码较为复杂,理解起来有所难度,我对官方源码做了一些精简,下文将围绕这个精简版的ConcurrentDictionary展开叙述. 38 | 39 | 40 | ## 数据结构 41 | 42 | ### Node 43 | 44 | ConcurrentDictionary中的每个数据存储在一个Node中,它除了存储value信息,还存储key信息,以及key对应的hashcode 45 | 46 | ``` C# 47 | private class Node 48 | { 49 | internal TKey m_key; //数据的key 50 | internal TValue m_value; //数据值 51 | internal volatile Node m_next; //当前Node的下级节点 52 | internal int m_hashcode; //key的hashcode 53 | 54 | //构造函数 55 | internal Node(TKey key, TValue value, int hashcode, Node next) 56 | { 57 | m_key = key; 58 | m_value = value; 59 | m_next = next; 60 | m_hashcode = hashcode; 61 | } 62 | } 63 | ``` 64 | 65 | ### Table 66 | 67 | 而整个ConcurrentDictionary的数据存储在这样的一个Table中,其中m_buckets的Index负责映射key,m_locks是线程锁,下文中会有详细介绍,m_countPerLock存储每个lock锁负责的node数量. 68 | 69 | ``` C# 70 | 71 | private class Tables 72 | { 73 | internal readonly Node[] m_buckets; //上文中提到的buckets 74 | internal readonly object[] m_locks; //线程锁 75 | internal volatile int[] m_countPerLock; //索格锁所管理的数据数量 76 | internal readonly IEqualityComparer m_comparer; //当前key对应的type的比较器 77 | 78 | //构造函数 79 | internal Tables(Node[] buckets, object[] locks, int[] countPerlock, IEqualityComparer comparer) 80 | { 81 | m_buckets = buckets; 82 | m_locks = locks; 83 | m_countPerLock = countPerlock; 84 | m_comparer = comparer; 85 | } 86 | } 87 | 88 | ``` 89 | 90 | ConcurrentDictionary会在构造函数中创建Table,这里我对原有的构造函数进行了简化,通过默认值进行创建,其中DefaultConcurrencyLevel默认并发级别为当前计算机处理器的线程数. 91 | 92 | ``` C# 93 | //构造函数 94 | public ConcurrentDictionaryMini() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, 95 | EqualityComparer.Default) 96 | { 97 | } 98 | 99 | /// 100 | /// 101 | /// 102 | /// 并发等级,默认为CPU的线程数 103 | /// 默认容量,31,超过31后会自动扩容 104 | /// 时否动态扩充锁的数量 105 | /// key的比较器 106 | internal ConcurrentDictionaryMini(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) 107 | { 108 | if (concurrencyLevel < 1) 109 | { 110 | throw new Exception("concurrencyLevel 必须为正数"); 111 | } 112 | 113 | if (capacity < 0) 114 | { 115 | throw new Exception("capacity 不能为负数."); 116 | } 117 | 118 | if (capacity < concurrencyLevel) 119 | { 120 | capacity = concurrencyLevel; 121 | } 122 | 123 | object[] locks = new object[concurrencyLevel]; 124 | for (int i = 0; i < locks.Length; i++) 125 | { 126 | locks[i] = new object(); 127 | } 128 | 129 | int[] countPerLock = new int[locks.Length]; 130 | Node[] buckets = new Node[capacity]; 131 | m_tables = new Tables(buckets, locks, countPerLock, comparer); 132 | 133 | m_growLockArray = growLockArray; 134 | m_budget = buckets.Length / locks.Length; 135 | } 136 | 137 | ``` 138 | 139 | ## 方法 140 | 141 | ConcurrentDictionary中较为基础重点的方法分别位Add,Get,Remove,Grow Table方法,其他方法基本上是建立在这四个方法的基础上进行的扩充. 142 | 143 | ### Add 144 | 145 | 向Table中添加元素有以下亮点值得我们关注. 146 | 147 | * 开始操作前会声明一个tables变量来存储操作开始前的m_tables,在正式开始操作后(进入lock)的时候,会检查tables在准备工作阶段是否别的线程改变,如果改变了,则重新开始准备工作并从新开始. 148 | 149 | * 通过GetBucketAndLockNo方法获取bucket索引以及lock索引,其内部就是取余操作. 150 | 151 | ``` C# 152 | private void GetBucketAndLockNo( 153 | int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) 154 | { 155 | //0x7FFFFFFF 是long int的最大值 与它按位与数据小于等于这个最大值 156 | bucketNo = (hashcode & 0x7fffffff) % bucketCount; 157 | lockNo = bucketNo % lockCount; 158 | } 159 | ``` 160 | 161 | * 对数据进行操作前会从m_locks取出第lockNo个对象最为lock,操作完成后释放该lock.多个lock一定程度上减少了阻塞的可能性. 162 | 163 | * 在对数据进行更新时,如果该Value的Type为允许原子性写入的,则直接更新该Value,否则创建一个新的node进行覆盖. 164 | 165 | ``` C# 166 | /// 167 | /// Determines whether type TValue can be written atomically 168 | /// 169 | private static bool IsValueWriteAtomic() 170 | { 171 | Type valueType = typeof(TValue); 172 | 173 | // 174 | // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without 175 | // the risk of tearing. 176 | // 177 | // See http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf 178 | // 179 | if (valueType.IsClass) 180 | { 181 | return true; 182 | } 183 | switch (Type.GetTypeCode(valueType)) 184 | { 185 | case TypeCode.Boolean: 186 | case TypeCode.Byte: 187 | case TypeCode.Char: 188 | case TypeCode.Int16: 189 | case TypeCode.Int32: 190 | case TypeCode.SByte: 191 | case TypeCode.Single: 192 | case TypeCode.UInt16: 193 | case TypeCode.UInt32: 194 | return true; 195 | 196 | case TypeCode.Int64: 197 | case TypeCode.Double: 198 | case TypeCode.UInt64: 199 | return IntPtr.Size == 8; 200 | 201 | default: 202 | return false; 203 | } 204 | } 205 | ``` 206 | 207 | 该方法依据CLI规范进行编写,简单来说,32位的计算机,对32字节以下的数据类型写入时可以一次写入的而不需要移动内存指针,64位计算机对64位以下的数据可一次性写入,不需要移动内存指针.保证了写入的安全. 208 | 详见12.6.6 209 | 210 | ``` C# 211 | 212 | private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) 213 | { 214 | while (true) 215 | { 216 | int bucketNo, lockNo; 217 | int hashcode; 218 | 219 | //https://www.cnblogs.com/blurhkh/p/10357576.html 220 | //需要了解一下值传递和引用传递 221 | Tables tables = m_tables; 222 | IEqualityComparer comparer = tables.m_comparer; 223 | hashcode = comparer.GetHashCode(key); 224 | 225 | GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); 226 | 227 | bool resizeDesired = false; 228 | bool lockTaken = false; 229 | 230 | try 231 | { 232 | if (acquireLock) 233 | Monitor.Enter(tables.m_locks[lockNo], ref lockTaken); 234 | 235 | //如果表刚刚调整了大小,我们可能没有持有正确的锁,必须重试。 236 | //当然这种情况很少见 237 | if (tables != m_tables) 238 | continue; 239 | 240 | Node prev = null; 241 | for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next) 242 | { 243 | if (comparer.Equals(node.m_key, key)) 244 | { 245 | //key在字典里找到了。如果允许更新,则更新该key的值。 246 | //我们需要为更新创建一个node,以支持不能以原子方式写入的TValue类型,因为free-lock 读取可能同时发生。 247 | if (updateIfExists) 248 | { 249 | if (s_isValueWriteAtomic) 250 | { 251 | node.m_value = value; 252 | } 253 | else 254 | { 255 | Node newNode = new Node(node.m_key, value, hashcode, node.m_next); 256 | if (prev == null) 257 | { 258 | tables.m_buckets[bucketNo] = newNode; 259 | } 260 | else 261 | { 262 | prev.m_next = newNode; 263 | } 264 | } 265 | 266 | resultingValue = value; 267 | } 268 | else 269 | { 270 | resultingValue = node.m_value; 271 | } 272 | 273 | return false; 274 | } 275 | 276 | prev = node; 277 | } 278 | 279 | //key没有在bucket中找到,则插入该数据 280 | Volatile.Write(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo])); 281 | //当m_countPerLock超过Int Max时会抛出OverflowException 282 | checked 283 | { 284 | tables.m_countPerLock[lockNo]++; 285 | } 286 | 287 | // 288 | // 如果m_countPerLock[lockNo] > m_budget,则需要调整buckets的大小。 289 | // GrowTable也可能会增加m_budget,但不会调整bucket table的大小。. 290 | // 如果发现bucket table利用率很低,也会发生这种情况。 291 | // 292 | if (tables.m_countPerLock[lockNo] > m_budget) 293 | { 294 | resizeDesired = true; 295 | } 296 | } 297 | finally 298 | { 299 | if (lockTaken) 300 | Monitor.Exit(tables.m_locks[lockNo]); 301 | } 302 | 303 | if (resizeDesired) 304 | { 305 | GrowTable(tables, tables.m_comparer, false, m_keyRehashCount); 306 | } 307 | 308 | resultingValue = value; 309 | return true; 310 | } 311 | } 312 | 313 | ``` 314 | 315 | ### Get 316 | 317 | 从Table中获取元素的的流程与前文介绍ConcurrentDictionary工作原理时一致,但有以下亮点值得关注. 318 | 319 | * 读取bucket[i]在Volatile.Read()方法中进行,该方法会自动对读取出来的数据加锁,避免在读取的过程中,数据被其他线程remove了. 320 | * Volatile读取指定字段时,在读取的内存中插入一个内存屏障,阻止处理器重新排序内存操作,如果在代码中此方法之后出现读取或写入,则处理器无法在此方法之前移动它。 321 | 322 | ``` C# 323 | 324 | public bool TryGetValue(TKey key, out TValue value) 325 | { 326 | if (key == null) throw new ArgumentNullException("key"); 327 | 328 | // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize. 329 | Tables tables = m_tables; 330 | IEqualityComparer comparer = tables.m_comparer; 331 | GetBucketAndLockNo(comparer.GetHashCode(key), out var bucketNo, out _, tables.m_buckets.Length, tables.m_locks.Length); 332 | 333 | // We can get away w/out a lock here. 334 | // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. 335 | Node n = Volatile.Read(ref tables.m_buckets[bucketNo]); 336 | 337 | while (n != null) 338 | { 339 | if (comparer.Equals(n.m_key, key)) 340 | { 341 | value = n.m_value; 342 | return true; 343 | } 344 | n = n.m_next; 345 | } 346 | 347 | value = default(TValue); 348 | return false; 349 | } 350 | 351 | ``` 352 | 353 | ### Remove 354 | 355 | Remove方法实现其实也并不复杂,类似我们链表操作中移除某个Node.移除节点的同时,还要对前后节点进行链接,相信一块小伙伴们肯定很好理解. 356 | 357 | ``` C# 358 | private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue) 359 | { 360 | while (true) 361 | { 362 | Tables tables = m_tables; 363 | 364 | IEqualityComparer comparer = tables.m_comparer; 365 | 366 | int bucketNo, lockNo; 367 | 368 | GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); 369 | 370 | lock (tables.m_locks[lockNo]) 371 | { 372 | if (tables != m_tables) 373 | continue; 374 | 375 | Node prev = null; 376 | for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next) 377 | { 378 | if (comparer.Equals(curr.m_key, key)) 379 | { 380 | if (matchValue) 381 | { 382 | bool valuesMatch = EqualityComparer.Default.Equals(oldValue, curr.m_value); 383 | if (!valuesMatch) 384 | { 385 | value = default(TValue); 386 | return false; 387 | } 388 | } 389 | if (prev == null) 390 | Volatile.Write(ref tables.m_buckets[bucketNo], curr.m_next); 391 | else 392 | { 393 | prev.m_next = curr.m_next; 394 | } 395 | 396 | value = curr.m_value; 397 | tables.m_countPerLock[lockNo]--; 398 | return true; 399 | } 400 | 401 | prev = curr; 402 | } 403 | } 404 | 405 | value = default(TValue); 406 | return false; 407 | } 408 | } 409 | ``` 410 | 411 | ### Grow table 412 | 413 | 当table中任何一个m_countPerLock的数量超过了设定的阈值后,会触发此操作对Table进行扩容. 414 | 415 | ``` C# 416 | private void GrowTable(Tables tables, IEqualityComparer newComparer, bool regenerateHashKeys, 417 | int rehashCount) 418 | { 419 | int locksAcquired = 0; 420 | try 421 | { 422 | //首先锁住第一个lock进行resize操作. 423 | AcquireLocks(0, 1, ref locksAcquired); 424 | 425 | if (regenerateHashKeys && rehashCount == m_keyRehashCount) 426 | { 427 | tables = m_tables; 428 | } 429 | else 430 | { 431 | if (tables != m_tables) 432 | return; 433 | 434 | long approxCount = 0; 435 | for (int i = 0; i < tables.m_countPerLock.Length; i++) 436 | { 437 | approxCount += tables.m_countPerLock[i]; 438 | } 439 | 440 | //如果bucket数组太空,则将预算加倍,而不是调整表的大小 441 | if (approxCount < tables.m_buckets.Length / 4) 442 | { 443 | m_budget = 2 * m_budget; 444 | if (m_budget < 0) 445 | { 446 | m_budget = int.MaxValue; 447 | } 448 | 449 | return; 450 | } 451 | } 452 | 453 | int newLength = 0; 454 | bool maximizeTableSize = false; 455 | try 456 | { 457 | checked 458 | { 459 | newLength = tables.m_buckets.Length * 2 + 1; 460 | while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) 461 | { 462 | newLength += 2; 463 | } 464 | } 465 | } 466 | catch (OverflowException) 467 | { 468 | maximizeTableSize = true; 469 | } 470 | 471 | if (maximizeTableSize) 472 | { 473 | newLength = int.MaxValue; 474 | 475 | m_budget = int.MaxValue; 476 | } 477 | 478 | AcquireLocks(1, tables.m_locks.Length, ref locksAcquired); 479 | 480 | object[] newLocks = tables.m_locks; 481 | 482 | //Add more locks 483 | if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER) 484 | { 485 | newLocks = new object[tables.m_locks.Length * 2]; 486 | Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length); 487 | 488 | for (int i = tables.m_locks.Length; i < newLocks.Length; i++) 489 | { 490 | newLocks[i] = new object(); 491 | } 492 | } 493 | 494 | Node[] newBuckets = new Node[newLength]; 495 | int[] newCountPerLock = new int[newLocks.Length]; 496 | 497 | for (int i = 0; i < tables.m_buckets.Length; i++) 498 | { 499 | Node current = tables.m_buckets[i]; 500 | while (current != null) 501 | { 502 | Node next = current.m_next; 503 | int newBucketNo, newLockNo; 504 | int nodeHashCode = current.m_hashcode; 505 | 506 | if (regenerateHashKeys) 507 | { 508 | //Recompute the hash from the key 509 | nodeHashCode = newComparer.GetHashCode(current.m_key); 510 | } 511 | 512 | GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, 513 | newLocks.Length); 514 | 515 | newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, 516 | newBuckets[newBucketNo]); 517 | checked 518 | { 519 | newCountPerLock[newLockNo]++; 520 | } 521 | 522 | current = next; 523 | } 524 | } 525 | 526 | if (regenerateHashKeys) 527 | { 528 | unchecked 529 | { 530 | m_keyRehashCount++; 531 | } 532 | } 533 | 534 | m_budget = Math.Max(1, newBuckets.Length / newLocks.Length); 535 | 536 | m_tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer); 537 | } 538 | finally 539 | { 540 | ReleaseLocks(0, locksAcquired); 541 | } 542 | } 543 | 544 | ``` 545 | 546 | # 学习感悟 547 | 548 | * `lock[]`:在以往的线程安全上,我们对数据的保护往往是对数据的修改写入等地方加上lock,这个lock经常上整个上下文中唯一的,这样的设计下就可能会出现多个线程,写入的根本不是一块数据,却要等待前一个线程写入完成下一个线程才能继续操作.在ConcurrentDictionary中,通过哈希算法,从数组`lock[]`中找出key的准确lock,如果不同的key,使用的不是同一个lock,那么这多个线程的写入时互不影响的. 549 | 550 | * 写入要考虑线程安全,读取呢?不可否认,在大部分场景下,读取不必去考虑线程安全,但是在我们这样的链式读取中,需要自上而下地查找,是不是有种可能在查找个过程中,链路被修改了呢?所以ConcurrentDictionary中使用Volatile.Read来读取出数据,该方法从指定字段读取对象引用,在需要它的系统上,插入一个内存屏障,阻止处理器重新排序内存操作,如果在代码中此方法之后出现读取或写入,则处理器无法在此方法之前移动它。 551 | 552 | * 在ConcurrentDictionary的更新方法中,对数据进行更新时,会判断该数据是否可以原子写入,如果时可以原子写入的,那么就直接更新数据,如果不是,那么会创建一个新的node覆盖原有node,起初看到这里时候,我百思不得其解,不知道这么操作的目的,后面在jeo duffy的博客中[Thread-safety, torn reads, and the like](http://joeduffyblog.com/2006/02/07/threadsafety-torn-reads-and-the-like/)中找到了答案,这样操作时为了防止torn reads(撕裂读取),什么叫撕裂读取呢?通俗地说,就是有的数据类型写入时,要分多次写入,写一次,移动一次指针,那么就有可能写了一半,这个结果被另外一个线程读取走了.比如说我把 `刘振宇`三个字改成`周杰伦`的过程中,我先把刘改成周了,正在我准备去把振改成杰的时候,另外一个线程过来读取结果了,读到的数据是`周振宇`,这显然是不对的.所以对这种,更安全的做法是先把`周杰伦`三个字写好在一张纸条上,然后直接替换掉`刘振宇`.更多信息在[CLI规范12.6.6](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf)有详细介绍. 553 | 554 | * `checked`和`unckecked`关键字.非常量的运算(non-constant)运算在编译阶段和运行时下不会做溢出检查,如下这样的代码时不会抛出异常的,算错了也不会报错。 555 | 556 | ``` C# 557 | int ten = 10; 558 | int i2 = 2147483647 + ten; 559 | ``` 560 | 但是我们知道,int的最大值是2147483647,如果我们将上面这样的代码嵌套在`checked`就会做溢出检查了. 561 | ``` C# 562 | checked 563 | { 564 | int ten = 10; 565 | int i2 = 2147483647 + ten; 566 | } 567 | ``` 568 | 相反,对于常量,编译时是会做溢出检查的,下面这样的代码在编译时就会报错的,如果我们使用`unckeck`标签进行标记,则在编译阶段不会做移除检查. 569 | ``` C# 570 | int a = int.MaxValue * 2; 571 | ``` 572 | 那么问题来了,我们当然知道checked很有用,那么uncheck呢?如果我们只是需要那么一个数而已,至于溢出不溢出的关系不大,比如说生成一个对象的HashCode,比如说根据一个算法计算出一个相对随机数,这都是不需要准确结果的,ConcurrentDictionary中对于`m_keyRehashCount++`这个运算就使用了unchecked,就是因为m_keyRehashCount是用来生成哈希值的,我们并不关心它有没有溢出. 573 | 574 | * `volatile`关键字,表示一个字段可能是由在同一时间执行多个线程进行修改。出于性能原因,编译器\运行时系统甚至硬件可以重新排列对存储器位置的读取和写入。声明的字段volatile不受这些优化的约束。添加volatile修饰符可确保所有线程都能按照执行顺序由任何其他线程执行的易失性写入,易失性写入是一件疯狂的事情的事情:[普通玩家慎用](https://stackoverflow.com/questions/72275/when-should-the-volatile-keyword-be-used-in-c). 575 | 576 | 本博客所涉及的代码都保存在github中,Take it easy to enjoy it! 577 | -------------------------------------------------------------------------------- /DictionaryMini.md: -------------------------------------------------------------------------------- 1 | 2 | # 从一道亲身经历的面试题说起 3 | 4 | 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点. 5 | ![Y形链表](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/chain.gif) 6 | 为了便于描述,我把上面的那条线路称为线路1,下面的称为线路2. 7 | 8 | ## 思路1 9 | 10 | 先判断线路1的第一个节点的下级节点是否是线路2的第一个节点,如果不是,再判断是不是线路2的第二个,如果也不是,判断是不是第三个节点,一直到最后一个. 11 | 如果第一轮没找到,再按以上思路处理线路一的第二个节点,第三个,第四个... 找到为止. 12 | 时间复杂度n2,相信如果我用的是这种方法,可肯定被Pass了. 13 | 14 | ## 思路2 15 | 16 | 首先,我遍历线路2的所有节点,把节点的索引作为key,下级节点索引作为value存入字典中. 17 | 然后,遍历线路1中节点,判断字典中是否包含该节点的下级节点索引的key,即`dic.ContainsKey((node.next)` ,如果包含,那么该下级节点就是交叉节点了. 18 | 时间复杂度是n. 19 | 那么问题来了,面试官问我了,为什么时间复杂度n呢?你有没有研究过字典的`ContainsKey`这个方法呢?难道它不是通过遍历内部元素来判断Key是否存在的呢?如果是的话,那时间复杂度还是n2才是呀? 20 | 我当时支支吾吾,确实不明白字典的工作原理,厚着面皮说 "不是的,它是通过哈希表直接拿出来的,不用遍历",面试官这边是敷衍过去了,但在我心里却留下了一个谜,已经入职半年多了,欠下的技术债是时候还了. 21 | 22 | # 带着问题来阅读 23 | 24 | 在看这篇文章前,不知道您使用字典的时候是否有过这样的疑问. 25 | 26 | 1. 字典为什么能无限地Add呢? 27 | 2. 从字典中取Item速度非常快,为什么呢? 28 | 3. 初始化字典可以指定字典容量,这是否多余呢? 29 | 4. 字典的桶`buckets` 长度为素数,为什么呢? 30 | 31 | 不管您以前有没有在心里问过自己这些问题,也不管您是否已经有了自己得答案,都让我们带着这几个问题接着往下走. 32 | 33 | # 从哈希函数说起 34 | 35 | 什么是哈希函数? 36 | 哈希函数又称散列函数,是一种从任何一种数据中创建小的数字“指纹”的方法。 37 | 下面,我们看看JDK中Sting.GetHashCode()方法. 38 | 39 | ``` java 40 | public int hashCode() { 41 | int h = hash; 42 | //hash default value : 0 43 | if (h == 0 && value.length > 0) { 44 | //value : char storage 45 | char val[] = value; 46 | 47 | for (int i = 0; i < value.length; i++) { 48 | h = 31 * h + val[i]; 49 | } 50 | hash = h; 51 | } 52 | return h; 53 | } 54 | 55 | ``` 56 | 57 | 可以看到,无论多长的字符串,最终都会返回一个int值,当哈希函数确定的情况下,任何一个字符串的哈希值都是唯一且确定的. 58 | 当然,这里只是找了一种最简单的字符数哈希值求法,理论上只要能把一个对象转换成唯一且确定值的函数,我们都可以把它称之为哈希函数. 59 | 这是哈希函数的示意图. 60 | ![哈希函数示意图](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/HashFunction.svg?sanitize=true) 61 | 所以,`一个对象的哈希值是确定且唯一的!`. 62 | 63 | # 字典 64 | 65 | 如何把哈希值和在集合中我们要的数据的地址关联起来呢?解开这个疑惑前我来看看一个这样不怎么恰当的例子: 66 | 67 | 有一天,我不小心干了什么坏事,警察叔叔没有逮到我本人,但是他知道是一个叫`阿宇`的干的,他要找我肯定先去我家,他怎么知道我家的地址呢?他不可能在全中国的家庭一个个去遍历,敲门,问`阿宇`是你们家的熊孩子吗? 68 | 69 | 正常应该是通过我的名字,找到我的身份证号码,然后我的身份证上登记着我的家庭地址(我们假设一个名字只能找到一张身份证). 70 | 71 | `阿宇`-----> 身份证(身份证号码,家庭住址)------>我家 72 | 73 | 我们就可以把由阿宇找到身份证号码的过程,理解为`哈希函数`,身份证存储着我的号码的同时,也存储着我家的地址,身份证这个角色在字典中就是 `bucket`,它起一个桥梁作用,当有人要找阿宇家在哪时,直接问它,准备错的,字典中,bucket存储着数据的内存地址(索引),我们要知道key对应的数据的内存地址,问buckets要就对了. 74 | 75 | key--->bucket的过程 ~= `阿宇`----->身份证 的过程. 76 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/hashtable0.svg?sanitize=true) 77 | 78 | 警察叔叔通过家庭住址找到了我家之后,我家除了住我,还住着我爸,我妈,他敲门的时候,是我爸开门,于是问我爸爸,`阿宇`在哪,我爸不知道,我爸便问我妈,儿子在哪?我妈告诉警察叔叔,我在书房呢.很好,警察叔叔就这样把我给逮住了. 79 | 80 | 字典也是这样,因为key的哈希值范围很大的,我们不可能声明一个这么大的数组作为buckets,这样就太浪费了,我们做法时HashCode%BucketSize作为bucket的索引. 81 | 82 | 假设Bucket的长度3,那么当key1的HashCode为2时,它数据地址就问buckets[2](2%3=2)要,当key2的HashCode为5时,它的数据地址也是问buckets[2](5%3=2)要的. 83 | 84 | 这就导致同一个bucket可能有多个key对应,即下图中的Johon Smith和Sandra Dee,但是bucket只能记录一个内存地址(索引),也就是警察叔叔通过家庭地址找到我家时,正常来说,只有一个人过来开门,那么,如何找到也在这个家里的我的呢?我爸记录这我妈在厨房,我妈记录着我在书房,就这样,我就被揪出来了,我爸,我妈,我 就是字典中的一个entry. 85 | 86 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/hashtable1.svg?sanitize=true) 87 | 如果有一天,我妈妈老来得子又生了一个小宝宝,怎么办呢?很简单,我妈记录小宝宝的位置,那么我的只能巴结小宝宝,让小宝宝来记录我的位置了. 88 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/hashtable2.svg?sanitize=true) 89 | ![Alt text](https://raw.githubusercontent.com/liuzhenyulive/DictionaryMini/master/Pic/hashtable3.svg?sanitize=true) 90 | 91 | 既然大的原理明白了,是不是要看看源码,来研究研究代码中字典怎么实现的呢? 92 | 93 | # DictionaryMini 94 | 95 | 上次在苏州参加苏州微软技术俱乐部成立大会时,有幸参加了`蒋金楠` 老师讲的Asp .net core框架解密,蒋老师有句话让我印象很深刻,"学好一门技术的最好的方法,就是模仿它的样子,自己造一个出来"于是他弄了个Asp .net core mini,所以我效仿蒋老师,弄了个DictionaryMini 96 | 97 | 其源代码我放在了Github仓库,有兴趣的可以看看: 98 | 99 | 我觉得字典这几个方面值得了解一下: 100 | 101 | 1. 数据存储的最小单元的数据结构 102 | 2. 字典的初始化 103 | 3. 添加新元素 104 | 4. 字典的扩容 105 | 5. 移除元素 106 | 107 | 字典中还有其他功能,但我相信,只要弄明白的这几个方面的工作原理,我们也就恰中肯綮,他么问题也就迎刃而解了. 108 | 109 | ## 数据存储的最小单元(Entry)的数据结构 110 | 111 | ``` c# 112 | private struct Entry 113 | { 114 | public int HashCode; 115 | public int Next; 116 | public TKey Key; 117 | public TValue Value; 118 | } 119 | ``` 120 | 121 | 一个Entry包括该key的HashCode,以及下个Entry的索引Next,该键值对的Key以及数据Vaule. 122 | 123 | ## 字典初始化 124 | 125 | ``` c# 126 | private void Initialize(int capacity) 127 | { 128 | int size = HashHelpersMini.GetPrime(capacity); 129 | _buckets = new int[size]; 130 | for (int i = 0; i < _buckets.Length; i++) 131 | { 132 | _buckets[i] = -1; 133 | } 134 | 135 | _entries = new Entry[size]; 136 | 137 | _freeList = -1; 138 | } 139 | ``` 140 | 141 | 字典初始化时,首先要创建int数组,分别作为buckets和entries,其中buckets的index是key的`哈希值%size`,它的value是数据在entries中的index,我们要取的数据就存在entries中.当某一个bucket没有指向任何entry时,它的value为-1. 142 | 另外,很有意思得一点,buckets的数组长度是多少呢?这个我研究了挺久,发现取的是大于capacity的最小质数. 143 | 144 | ## 添加新元素 145 | 146 | ``` c# 147 | private void Insert(TKey key, TValue value, bool add) 148 | { 149 | if (key == null) 150 | { 151 | throw new ArgumentNullException(); 152 | } 153 | //如果buckets为空,则重新初始化字典. 154 | if (_buckets == null) Initialize(0); 155 | //获取传入key的 哈希值 156 | var hashCode = _comparer.GetHashCode(key); 157 | //把hashCode%size的值作为目标Bucket的Index. 158 | var targetBucket = hashCode % _buckets.Length; 159 | //遍历判断传入的key对应的值是否已经添加字典中 160 | for (int i = _buckets[targetBucket]; i > 0; i = _entries[i].Next) 161 | { 162 | if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) 163 | { 164 | //当add为true时,直接抛出异常,告诉给定的值已存在在字典中. 165 | if (add) 166 | { 167 | throw new Exception("给定的关键字已存在!"); 168 | } 169 | //当add为false时,重新赋值并退出. 170 | _entries[i].Value = value; 171 | return; 172 | } 173 | } 174 | //表示本次存储数据的数据在Entries中的索引 175 | int index; 176 | //当有数据被Remove时,freeCount会加1 177 | if (_freeCount > 0) 178 | { 179 | //freeList为上一个移除数据的Entries的索引,这样能尽量地让连续的Entries都利用起来. 180 | index = _freeList; 181 | _freeList = _entries[index].Next; 182 | _freeCount--; 183 | } 184 | else 185 | { 186 | //当已使用的Entry的数据等于Entries的长度时,说明字典里的数据已经存满了,需要对字典进行扩容,Resize. 187 | if (_count == _entries.Length) 188 | { 189 | Resize(); 190 | targetBucket = hashCode % _buckets.Length; 191 | } 192 | //默认取未使用的第一个 193 | index = _count; 194 | _count++; 195 | } 196 | //对Entries进行赋值 197 | _entries[index].HashCode = hashCode; 198 | _entries[index].Next = _buckets[targetBucket]; 199 | _entries[index].Key = key; 200 | _entries[index].Value = value; 201 | //用buckets来登记数据在Entries中的索引. 202 | _buckets[targetBucket] = index; 203 | } 204 | ``` 205 | 206 | ## 字典的扩容 207 | 208 | ``` c# 209 | private void Resize() 210 | { 211 | //获取大于当前size的最小质数 212 | Resize(HashHelpersMini.GetPrime(_count), false); 213 | } 214 | private void Resize(int newSize, bool foreNewHashCodes) 215 | { 216 | var newBuckets = new int[newSize]; 217 | //把所有buckets设置-1 218 | for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; 219 | var newEntries = new Entry[newSize]; 220 | //把旧的的Enties中的数据拷贝到新的Entires数组中. 221 | Array.Copy(_entries, 0, newEntries, 0, _count); 222 | if (foreNewHashCodes) 223 | { 224 | for (int i = 0; i < _count; i++) 225 | { 226 | if (newEntries[i].HashCode != -1) 227 | { 228 | newEntries[i].HashCode = _comparer.GetHashCode(newEntries[i].Key); 229 | } 230 | } 231 | } 232 | //重新对新的bucket赋值. 233 | for (int i = 0; i < _count; i++) 234 | { 235 | if (newEntries[i].HashCode > 0) 236 | { 237 | int bucket = newEntries[i].HashCode % newSize; 238 | newEntries[i].Next = newBuckets[bucket]; 239 | newBuckets[bucket] = i; 240 | } 241 | } 242 | 243 | _buckets = newBuckets; 244 | _entries = newEntries; 245 | } 246 | ``` 247 | 248 | ## 移除元素 249 | 250 | ``` c# 251 | //通过key移除指定的item 252 | public bool Remove(TKey key) 253 | { 254 | if (key == null) 255 | throw new Exception(); 256 | 257 | if (_buckets != null) 258 | { 259 | //获取该key的HashCode 260 | int hashCode = _comparer.GetHashCode(key); 261 | //获取bucket的索引 262 | int bucket = hashCode % _buckets.Length; 263 | int last = -1; 264 | for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].Next) 265 | { 266 | if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) 267 | { 268 | if (last < 0) 269 | { 270 | _buckets[bucket] = _entries[i].Next; 271 | } 272 | else 273 | { 274 | _entries[last].Next = _entries[i].Next; 275 | } 276 | //把要移除的元素置空. 277 | _entries[i].HashCode = -1; 278 | _entries[i].Next = _freeList; 279 | _entries[i].Key = default(TKey); 280 | _entries[i].Value = default(TValue); 281 | //把该释放的索引记录在freeList中 282 | _freeList = i; 283 | //把空Entry的数量加1 284 | _freeCount++; 285 | return true; 286 | } 287 | } 288 | } 289 | 290 | return false; 291 | } 292 | ``` 293 | 294 | 我对.Net中的Dictionary的源码进行了精简,做了一个DictionaryMini,有兴趣的可以到我的github查看相关代码. 295 | 296 | 297 | # 答疑时间 298 | 299 | ## 字典为什么能无限地Add呢 300 | 301 | 向Dictionary中添加元素时,会有一步进行判断字典是否满了,如果满了,会用Resize对字典进行自动地扩容,所以字典不会向数组那样有固定的容量. 302 | 303 | ## 为什么从字典中取数据这么快 304 | 305 | Key-->HashCode-->HashCode%Size-->Bucket Index-->Bucket-->Entry Index-->Value 306 | 整个过程都没有通过`遍历`来查找数据,一步到下一步的目的性时非常明确的,所以取数据的过程非常快. 307 | 308 | ## 初始化字典可以指定字典容量,这是否多余呢 309 | 310 | 前面说过,当向字典中插入数据时,如果字典已满,会自动地给字典Resize扩容. 311 | 扩容的标准时会把大于当前前容量的最小质数作为当前字典的容量,比如,当我们的字典最终存储的元素为15个时,会有这样的一个过程. 312 | new Dictionary()------------------->size:3 313 | 字典添加低3个元素---->Resize--->size:7 314 | 字典添加低7个元素---->Resize--->size:11 315 | 字典添加低11个元素--->Resize--->size:23 316 | 317 | 可以看到一共进行了三次次Resize,如果我们预先知道最终字典要存储15个元素,那么我们可以用new Dictionary(15)来创建一个字典. 318 | 319 | new Dictionary(15)---------->size:23 320 | 321 | 这样就不需要进行Resize了,可以想象,每次Resize都是消耗一定的时间资源的,需要把OldEnties Copy to NewEntries 所以我们在创建字典时,如果知道字典的中要存储的字典的元素个数,在创建字典时,就传入capacity,免去了中间的Resize进行扩容. 322 | 323 | Tips: 324 | 即使指定字典容量capacity,后期如果添加的元素超过这个数量,字典也是会自动扩容的. 325 | 326 | ## 为什么字典的桶buckets 长度为素数 327 | 328 | 我们假设有这样的一系列keys,他们的分布范围时K={ 0, 1,..., 100 },又假设某一个buckets的长度m=12,因为3是12的一个因子,当key时3的倍数时,那么targetBucket也将会是3的倍数. 329 | 330 | ``` c# 331 | Keys {0,12,24,36,...} 332 | TargetBucket将会是0. 333 | Keys {3,15,27,39,...} 334 | TargetBucket将会是3. 335 | Keys {6,18,30,42,...} 336 | TargetBucket将会是6. 337 | Keys {9,21,33,45,...} 338 | TargetBucket将会是9. 339 | ``` 340 | 341 | 如果Key的值是均匀分布的(K中的每一个Key中出现的可能性相同),那么Buckets的Length就没有那么重要了,但是如果Key不是均匀分布呢? 342 | 想象一下,如果Key在3的倍数时出现的可能性特别大,其他的基本不出现,TargetBucket那些不是3的倍数的索引就基本不会存储什么数据了,这样就可能有2/3的Bucket空着,数据大量第聚集在0,3,6,9中. 343 | 这种情况其实时很常见的。 例如,又一种场景,您根据对象存储在内存中的位置来跟踪对象,如果你的计算机的字节大小是4,而且你的Buckets的长度也为4,那么所有的内存地址都会时4的倍数,也就是说key都是4的倍数,它的HashCode也将会时4的倍数,导致所有的数据都会存储在TargetBucket=0(Key%4=0)的bucket中,而剩下的3/4的Buckets都是空的. 这样数据分布就非常不均匀了. 344 | K中的每一个key如果与Buckets的长度m有公因子,那么该数据就会存储在这个公因子的倍数为索引的bucket中.为了让数据尽可能地均匀地分布在Buckets中,我们要尽量减少m和K中的key的有公因子出现的可能性.那么,把Bucket的长度设为质数就是最佳选择了,因为质数的因子时最少的.这就是为什么每次利用Resize给字典扩容时会取大于当前size的最小质数的原因. 345 | 确实,这一块可能有点难以理解,我花了好几天才研究明白,如果小伙伴们没有看懂建议看看这里. 346 | 347 | 348 | 最后,感谢大家耐着性子把这篇文章看完,欢迎fork DictionaryMini进行进一步的研究,谢谢大家的支持. 349 | -------------------------------------------------------------------------------- /DictionaryMini/DictionaryMini.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.271 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DictionaryMini", "DictionaryMini\DictionaryMini.csproj", "{6EA44F47-93A8-431B-8C9E-2D9356E01602}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{9E7B4963-4409-4032-BFC5-BC76EC2D749D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6EA44F47-93A8-431B-8C9E-2D9356E01602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6EA44F47-93A8-431B-8C9E-2D9356E01602}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6EA44F47-93A8-431B-8C9E-2D9356E01602}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6EA44F47-93A8-431B-8C9E-2D9356E01602}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9E7B4963-4409-4032-BFC5-BC76EC2D749D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9E7B4963-4409-4032-BFC5-BC76EC2D749D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9E7B4963-4409-4032-BFC5-BC76EC2D749D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9E7B4963-4409-4032-BFC5-BC76EC2D749D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {B64D9DD4-DAF2-41BD-B0EA-DEF969A92F94} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DictionaryMini/DictionaryMini/ConcurrentDictionaryMini.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace DictionaryMini 6 | { 7 | public class ConcurrentDictionaryMini 8 | { 9 | private class Node 10 | { 11 | internal TKey m_key; //数据的key 12 | internal TValue m_value; //数据值 13 | internal volatile Node m_next; //当前Node的下级节点 14 | internal int m_hashcode; //key的hashcode 15 | 16 | //构造函数 17 | internal Node(TKey key, TValue value, int hashcode, Node next) 18 | { 19 | m_key = key; 20 | m_value = value; 21 | m_next = next; 22 | m_hashcode = hashcode; 23 | } 24 | } 25 | 26 | private class Tables 27 | { 28 | internal readonly Node[] m_buckets; //上文中提到的buckets 29 | internal readonly object[] m_locks; //线程锁 30 | internal volatile int[] m_countPerLock; //每个锁所管理的数据数量 31 | internal readonly IEqualityComparer m_comparer; //当前key对应的type的比较器 32 | 33 | //构造函数 34 | internal Tables(Node[] buckets, object[] locks, int[] countPerlock, IEqualityComparer comparer) 35 | { 36 | m_buckets = buckets; 37 | m_locks = locks; 38 | m_countPerLock = countPerlock; 39 | m_comparer = comparer; 40 | } 41 | } 42 | 43 | private volatile Tables m_tables; 44 | 45 | //时否动态扩充锁的数量 46 | private readonly bool m_growLockArray; 47 | 48 | private int m_keyRehashCount; 49 | 50 | //在触发调整大小操作之前,每个锁的最大元素数 51 | private int m_budget; 52 | 53 | private const int DEFAULT_CAPACITY = 31; 54 | 55 | private const int MAX_LOCK_NUMBER = 1024; 56 | 57 | //构造函数 58 | public ConcurrentDictionaryMini() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, 59 | EqualityComparer.Default) 60 | { 61 | } 62 | 63 | /// 64 | /// 65 | /// 66 | /// 并发等级,默认为CPU的线程数 67 | /// 默认容量,31,超过31后会自动扩容 68 | /// 时否动态扩充锁的数量 69 | /// key的比较器 70 | internal ConcurrentDictionaryMini(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) 71 | { 72 | if (concurrencyLevel < 1) 73 | { 74 | throw new Exception("concurrencyLevel 必须为正数"); 75 | } 76 | 77 | if (capacity < 0) 78 | { 79 | throw new Exception("capacity 不能为负数."); 80 | } 81 | 82 | if (capacity < concurrencyLevel) 83 | { 84 | capacity = concurrencyLevel; 85 | } 86 | 87 | object[] locks = new object[concurrencyLevel]; 88 | for (int i = 0; i < locks.Length; i++) 89 | { 90 | locks[i] = new object(); 91 | } 92 | 93 | int[] countPerLock = new int[locks.Length]; 94 | Node[] buckets = new Node[capacity]; 95 | m_tables = new Tables(buckets, locks, countPerLock, comparer); 96 | 97 | m_growLockArray = growLockArray; 98 | m_budget = buckets.Length / locks.Length; 99 | } 100 | 101 | public bool TryAdd(TKey key, TValue value) 102 | { 103 | if (key == null) throw new ArgumentNullException("key"); 104 | TValue dummy; 105 | return TryAddInternal(key, value, false, true, out dummy); 106 | } 107 | 108 | public bool TryGetValue(TKey key, out TValue value) 109 | { 110 | if (key == null) throw new ArgumentNullException("key"); 111 | 112 | // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize. 113 | Tables tables = m_tables; 114 | IEqualityComparer comparer = tables.m_comparer; 115 | GetBucketAndLockNo(comparer.GetHashCode(key), out var bucketNo, out _, tables.m_buckets.Length, tables.m_locks.Length); 116 | 117 | // We can get away w/out a lock here. 118 | // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. 119 | Node n = Volatile.Read(ref tables.m_buckets[bucketNo]); 120 | 121 | while (n != null) 122 | { 123 | if (comparer.Equals(n.m_key, key)) 124 | { 125 | value = n.m_value; 126 | return true; 127 | } 128 | n = n.m_next; 129 | } 130 | 131 | value = default(TValue); 132 | return false; 133 | } 134 | 135 | public bool ContainsKey(TKey key) 136 | { 137 | if (key == null) throw new ArgumentNullException("key"); 138 | 139 | return TryGetValue(key, out _); 140 | } 141 | 142 | public bool TryRemove(TKey key, out TValue value) 143 | { 144 | if (key == null) throw new ArgumentNullException("key"); 145 | 146 | return TryRemoveInternal(key, out value, false, default(TValue)); 147 | } 148 | 149 | public void Clear() 150 | { 151 | int locksAcquired = 0; 152 | try 153 | { 154 | AcquireAllLocks(ref locksAcquired); 155 | 156 | Tables newTables = new Tables(new Node[DEFAULT_CAPACITY], m_tables.m_locks, new int[m_tables.m_countPerLock.Length], m_tables.m_comparer); 157 | m_tables = newTables; 158 | m_budget = Math.Max(1, newTables.m_buckets.Length / newTables.m_locks.Length); 159 | } 160 | finally 161 | { 162 | ReleaseLocks(0, locksAcquired); 163 | } 164 | } 165 | 166 | public TValue this[TKey key] 167 | { 168 | get 169 | { 170 | TValue value; 171 | if (!TryGetValue(key, out value)) 172 | { 173 | throw new KeyNotFoundException(); 174 | } 175 | 176 | return value; 177 | } 178 | set 179 | { 180 | if (key == null) throw new ArgumentNullException("key"); 181 | TValue dummy; 182 | TryAddInternal(key, value, true, true, out dummy); 183 | } 184 | } 185 | 186 | #region Private 187 | 188 | private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue) 189 | { 190 | while (true) 191 | { 192 | Tables tables = m_tables; 193 | 194 | IEqualityComparer comparer = tables.m_comparer; 195 | 196 | int bucketNo, lockNo; 197 | 198 | GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); 199 | 200 | lock (tables.m_locks[lockNo]) 201 | { 202 | if (tables != m_tables) 203 | continue; 204 | 205 | Node prev = null; 206 | for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next) 207 | { 208 | if (comparer.Equals(curr.m_key, key)) 209 | { 210 | if (matchValue) 211 | { 212 | bool valuesMatch = EqualityComparer.Default.Equals(oldValue, curr.m_value); 213 | if (!valuesMatch) 214 | { 215 | value = default(TValue); 216 | return false; 217 | } 218 | } 219 | if (prev == null) 220 | Volatile.Write(ref tables.m_buckets[bucketNo], curr.m_next); 221 | else 222 | { 223 | prev.m_next = curr.m_next; 224 | } 225 | 226 | value = curr.m_value; 227 | tables.m_countPerLock[lockNo]--; 228 | return true; 229 | } 230 | 231 | prev = curr; 232 | } 233 | } 234 | 235 | value = default(TValue); 236 | return false; 237 | } 238 | } 239 | 240 | private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) 241 | { 242 | while (true) 243 | { 244 | int bucketNo, lockNo; 245 | int hashcode; 246 | 247 | //https://www.cnblogs.com/blurhkh/p/10357576.html 248 | //需要了解一下值传递和引用传递 249 | Tables tables = m_tables; 250 | IEqualityComparer comparer = tables.m_comparer; 251 | hashcode = comparer.GetHashCode(key); 252 | 253 | GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); 254 | 255 | bool resizeDesired = false; 256 | bool lockTaken = false; 257 | 258 | try 259 | { 260 | if (acquireLock) 261 | Monitor.Enter(tables.m_locks[lockNo], ref lockTaken); 262 | 263 | //如果表刚刚调整了大小,我们可能没有持有正确的锁,必须重试。 264 | //当然这种情况很少见 265 | if (tables != m_tables) 266 | continue; 267 | 268 | Node prev = null; 269 | for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next) 270 | { 271 | if (comparer.Equals(node.m_key, key)) 272 | { 273 | //key在字典里找到了。如果允许更新,则更新该key的值。 274 | //我们需要为更新创建一个node,以支持不能以原子方式写入的TValue类型,因为free-lock 读取可能同时发生。 275 | if (updateIfExists) 276 | { 277 | if (s_isValueWriteAtomic) 278 | { 279 | node.m_value = value; 280 | } 281 | else 282 | { 283 | Node newNode = new Node(node.m_key, value, hashcode, node.m_next); 284 | if (prev == null) 285 | { 286 | tables.m_buckets[bucketNo] = newNode; 287 | } 288 | else 289 | { 290 | prev.m_next = newNode; 291 | } 292 | } 293 | 294 | resultingValue = value; 295 | } 296 | else 297 | { 298 | resultingValue = node.m_value; 299 | } 300 | 301 | return false; 302 | } 303 | 304 | prev = node; 305 | } 306 | 307 | //key没有在bucket中找到,则插入该数据 308 | Volatile.Write(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo])); 309 | //当m_countPerLock超过Int Max时会抛出OverflowException 310 | checked 311 | { 312 | tables.m_countPerLock[lockNo]++; 313 | } 314 | 315 | // 316 | // 如果m_countPerLock[lockNo] > m_budget,则需要调整buckets的大小。 317 | // GrowTable也可能会增加m_budget,但不会调整bucket table的大小。. 318 | // 如果发现bucket table利用率很低,也会发生这种情况。 319 | // 320 | if (tables.m_countPerLock[lockNo] > m_budget) 321 | { 322 | resizeDesired = true; 323 | } 324 | } 325 | finally 326 | { 327 | if (lockTaken) 328 | Monitor.Exit(tables.m_locks[lockNo]); 329 | } 330 | 331 | if (resizeDesired) 332 | { 333 | GrowTable(tables, tables.m_comparer, false, m_keyRehashCount); 334 | } 335 | 336 | resultingValue = value; 337 | return true; 338 | } 339 | } 340 | 341 | private void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) 342 | { 343 | //0x7FFFFFFF 是long int的最大值 与它按位与数据小于等于这个最大值 344 | bucketNo = (hashcode & 0x7fffffff) % bucketCount; 345 | lockNo = bucketNo % lockCount; 346 | } 347 | 348 | // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) 349 | private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); 350 | 351 | private static int DefaultConcurrencyLevel 352 | { 353 | get { return Environment.ProcessorCount; } 354 | } 355 | 356 | /// 357 | /// Determines whether type TValue can be written atomically 358 | /// 359 | private static bool IsValueWriteAtomic() 360 | { 361 | Type valueType = typeof(TValue); 362 | 363 | // 364 | // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without 365 | // the risk of tearing. 366 | // 367 | // See http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf 368 | // 369 | if (valueType.IsClass) 370 | { 371 | return true; 372 | } 373 | switch (Type.GetTypeCode(valueType)) 374 | { 375 | case TypeCode.Boolean: 376 | case TypeCode.Byte: 377 | case TypeCode.Char: 378 | case TypeCode.Int16: 379 | case TypeCode.Int32: 380 | case TypeCode.SByte: 381 | case TypeCode.Single: 382 | case TypeCode.UInt16: 383 | case TypeCode.UInt32: 384 | return true; 385 | 386 | case TypeCode.Int64: 387 | case TypeCode.Double: 388 | case TypeCode.UInt64: 389 | return IntPtr.Size == 8; 390 | 391 | default: 392 | return false; 393 | } 394 | } 395 | 396 | private void GrowTable(Tables tables, IEqualityComparer newComparer, bool regenerateHashKeys, 397 | int rehashCount) 398 | { 399 | int locksAcquired = 0; 400 | try 401 | { 402 | //首先锁住第一个lock进行resize操作. 403 | AcquireLocks(0, 1, ref locksAcquired); 404 | 405 | if (regenerateHashKeys && rehashCount == m_keyRehashCount) 406 | { 407 | tables = m_tables; 408 | } 409 | else 410 | { 411 | if (tables != m_tables) 412 | return; 413 | 414 | long approxCount = 0; 415 | for (int i = 0; i < tables.m_countPerLock.Length; i++) 416 | { 417 | approxCount += tables.m_countPerLock[i]; 418 | } 419 | 420 | //如果bucket数组太空,则将预算加倍,而不是调整表的大小 421 | if (approxCount < tables.m_buckets.Length / 4) 422 | { 423 | m_budget = 2 * m_budget; 424 | if (m_budget < 0) 425 | { 426 | m_budget = int.MaxValue; 427 | } 428 | 429 | return; 430 | } 431 | } 432 | 433 | int newLength = 0; 434 | bool maximizeTableSize = false; 435 | try 436 | { 437 | checked 438 | { 439 | newLength = tables.m_buckets.Length * 2 + 1; 440 | while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) 441 | { 442 | newLength += 2; 443 | } 444 | } 445 | } 446 | catch (OverflowException) 447 | { 448 | maximizeTableSize = true; 449 | } 450 | 451 | if (maximizeTableSize) 452 | { 453 | newLength = int.MaxValue; 454 | 455 | m_budget = int.MaxValue; 456 | } 457 | 458 | AcquireLocks(1, tables.m_locks.Length, ref locksAcquired); 459 | 460 | object[] newLocks = tables.m_locks; 461 | 462 | //Add more locks 463 | if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER) 464 | { 465 | newLocks = new object[tables.m_locks.Length * 2]; 466 | Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length); 467 | 468 | for (int i = tables.m_locks.Length; i < newLocks.Length; i++) 469 | { 470 | newLocks[i] = new object(); 471 | } 472 | } 473 | 474 | Node[] newBuckets = new Node[newLength]; 475 | int[] newCountPerLock = new int[newLocks.Length]; 476 | 477 | for (int i = 0; i < tables.m_buckets.Length; i++) 478 | { 479 | Node current = tables.m_buckets[i]; 480 | while (current != null) 481 | { 482 | Node next = current.m_next; 483 | int newBucketNo, newLockNo; 484 | int nodeHashCode = current.m_hashcode; 485 | 486 | if (regenerateHashKeys) 487 | { 488 | //Recompute the hash from the key 489 | nodeHashCode = newComparer.GetHashCode(current.m_key); 490 | } 491 | 492 | GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, 493 | newLocks.Length); 494 | 495 | newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, 496 | newBuckets[newBucketNo]); 497 | checked 498 | { 499 | newCountPerLock[newLockNo]++; 500 | } 501 | 502 | current = next; 503 | } 504 | } 505 | 506 | if (regenerateHashKeys) 507 | { 508 | unchecked 509 | { 510 | m_keyRehashCount++; 511 | } 512 | } 513 | 514 | m_budget = Math.Max(1, newBuckets.Length / newLocks.Length); 515 | 516 | m_tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer); 517 | } 518 | finally 519 | { 520 | ReleaseLocks(0, locksAcquired); 521 | } 522 | } 523 | 524 | private void AcquireAllLocks(ref int locksAcquired) 525 | { 526 | // First, acquire lock 0 527 | AcquireLocks(0, 1, ref locksAcquired); 528 | 529 | // Now that we have lock 0, the m_locks array will not change (i.e., grow), 530 | // and so we can safely read m_locks.Length. 531 | AcquireLocks(1, m_tables.m_locks.Length, ref locksAcquired); 532 | } 533 | 534 | private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) 535 | { 536 | object[] locks = m_tables.m_locks; 537 | 538 | for (int i = fromInclusive; i < toExclusive; i++) 539 | { 540 | bool lockTaken = false; 541 | try 542 | { 543 | Monitor.Enter(locks[i], ref lockTaken); 544 | } 545 | finally 546 | { 547 | if (lockTaken) 548 | locksAcquired++; 549 | } 550 | } 551 | } 552 | 553 | private void ReleaseLocks(int fromInclusive, int toExclusive) 554 | { 555 | for (int i = fromInclusive; i < toExclusive; i++) 556 | { 557 | Monitor.Exit(m_tables.m_locks[i]); 558 | } 559 | } 560 | 561 | #endregion Private 562 | } 563 | } -------------------------------------------------------------------------------- /DictionaryMini/DictionaryMini/DictionaryMini.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DictionaryMini 5 | { 6 | public class DictionaryMini 7 | { 8 | //哈希中介 9 | private int[] _buckets; 10 | 11 | private Entry[] _entries; 12 | 13 | //当前空余的Entry数量 14 | private int _freeCount; 15 | 16 | //当前空余的Entry的索引 17 | private int _freeList; 18 | 19 | //当前字典的容量 20 | private int _count; 21 | 22 | //字典中数据存储的基础单元 23 | private struct Entry 24 | { 25 | public int HashCode; 26 | public int Next; 27 | public TKey Key; 28 | public TValue Value; 29 | } 30 | 31 | //针对TKey类型的对比器 32 | private readonly IEqualityComparer _comparer; 33 | 34 | public DictionaryMini() : this(0) 35 | { 36 | } 37 | 38 | public DictionaryMini(int capacity, IEqualityComparer comparer = null) 39 | { 40 | if (capacity < 0) 41 | throw new ArgumentOutOfRangeException(); 42 | if (capacity > 0) 43 | Initialize(capacity); 44 | _comparer = comparer ?? EqualityComparer.Default; 45 | } 46 | 47 | //添加Item 48 | public void Add(KeyValuePair item) 49 | { 50 | Insert(item.Key, item.Value, true); 51 | } 52 | 53 | //添加Item 54 | public void Add(TKey key, TValue value) 55 | { 56 | Insert(key, value, true); 57 | } 58 | 59 | //清空所有元素 60 | public void Clear() 61 | { 62 | if (_count > 0) 63 | { 64 | for (int i = 0; i < _buckets.Length; i++) _buckets[i] = -1; 65 | Array.Clear(_entries, 0, _count); 66 | _freeList = -1; 67 | _count = 0; 68 | _freeCount = 0; 69 | } 70 | } 71 | 72 | //判断是否包含指定key 73 | public bool ContainsKey(TKey key) 74 | { 75 | return FindEntry(key) >= 0; 76 | } 77 | 78 | //通过key移除指定的item 79 | public bool Remove(TKey key) 80 | { 81 | if (key == null) 82 | throw new Exception(); 83 | 84 | if (_buckets != null) 85 | { 86 | int hashCode = _comparer.GetHashCode(key); 87 | int bucket = hashCode % _buckets.Length; 88 | int last = -1; 89 | for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].Next) 90 | { 91 | if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) 92 | { 93 | if (last < 0) 94 | { 95 | _buckets[bucket] = _entries[i].Next; 96 | } 97 | else 98 | { 99 | _entries[last].Next = _entries[i].Next; 100 | } 101 | 102 | _entries[i].HashCode = -1; 103 | _entries[i].Next = _freeList; 104 | _entries[i].Key = default(TKey); 105 | _entries[i].Value = default(TValue); 106 | _freeList = i; 107 | _freeCount++; 108 | return true; 109 | } 110 | } 111 | } 112 | 113 | return false; 114 | } 115 | 116 | public TValue this[TKey key] 117 | { 118 | get 119 | { 120 | int i = FindEntry(key); 121 | if (i >= 0) return _entries[i].Value; 122 | throw new Exception("给定的关键字不在字典中!"); 123 | } 124 | set => Insert(key, value, false); 125 | } 126 | 127 | #region Private 128 | 129 | private void Initialize(int capacity) 130 | { 131 | int size = HashHelpersMini.GetPrime(capacity); 132 | _buckets = new int[size]; 133 | for (int i = 0; i < _buckets.Length; i++) 134 | { 135 | _buckets[i] = -1; 136 | } 137 | 138 | _entries = new Entry[size]; 139 | 140 | _freeList = -1; 141 | } 142 | 143 | private void Insert(TKey key, TValue value, bool add) 144 | { 145 | if (key == null) 146 | { 147 | throw new ArgumentNullException(); 148 | } 149 | //如果buckets为空,则重新初始化字典. 150 | if (_buckets == null) Initialize(0); 151 | //获取传入key的 哈希值 152 | var hashCode = _comparer.GetHashCode(key); 153 | //把hashCode%size的值作为目标Bucket的Index. 154 | var targetBucket = hashCode % _buckets.Length; 155 | //遍历判断传入的key对应的值是否已经添加字典中 156 | for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].Next) 157 | { 158 | if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) 159 | { 160 | //当add为true时,直接抛出异常,告诉给定的值已存在在字典中. 161 | if (add) 162 | { 163 | throw new Exception("给定的关键字已存在!"); 164 | } 165 | //当add为false时,重新赋值并退出. 166 | _entries[i].Value = value; 167 | return; 168 | } 169 | } 170 | //表示本次存储数据的数据在Entries中的索引 171 | int index; 172 | //当有数据被Remove时,freeCount会加1 173 | if (_freeCount > 0) 174 | { 175 | //freeList为上一个移除数据的Entries的索引,这样能尽量地让连续的Entries都利用起来. 176 | index = _freeList; 177 | _freeList = _entries[index].Next; 178 | _freeCount--; 179 | } 180 | else 181 | { 182 | //当已使用的Entry的数据等于Entries的长度时,说明字典里的数据已经存满了,需要对字典进行扩容,Resize. 183 | if (_count == _entries.Length) 184 | { 185 | Resize(); 186 | targetBucket = hashCode % _buckets.Length; 187 | } 188 | //默认取未使用的第一个 189 | index = _count; 190 | _count++; 191 | } 192 | //对Entries进行赋值 193 | _entries[index].HashCode = hashCode; 194 | _entries[index].Next = _buckets[targetBucket]; 195 | _entries[index].Key = key; 196 | _entries[index].Value = value; 197 | //用buckets来登记数据在Entries中的索引. 198 | _buckets[targetBucket] = index; 199 | } 200 | 201 | private void Resize() 202 | { 203 | Resize(HashHelpersMini.GetPrime(_count), false); 204 | } 205 | 206 | private void Resize(int newSize, bool foreNewHashCodes) 207 | { 208 | var newBuckets = new int[newSize]; 209 | for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; 210 | var newEntries = new Entry[newSize]; 211 | Array.Copy(_entries, 0, newEntries, 0, _count); 212 | if (foreNewHashCodes) 213 | { 214 | for (int i = 0; i < _count; i++) 215 | { 216 | if (newEntries[i].HashCode != -1) 217 | { 218 | newEntries[i].HashCode = _comparer.GetHashCode(newEntries[i].Key); 219 | } 220 | } 221 | } 222 | 223 | for (int i = 0; i < _count; i++) 224 | { 225 | if (newEntries[i].HashCode > 0) 226 | { 227 | int bucket = newEntries[i].HashCode % newSize; 228 | newEntries[i].Next = newBuckets[bucket]; 229 | newBuckets[bucket] = i; 230 | } 231 | } 232 | 233 | _buckets = newBuckets; 234 | _entries = newEntries; 235 | } 236 | 237 | private int FindEntry(TKey key) 238 | { 239 | if (_buckets != null) 240 | { 241 | int hashCode = _comparer.GetHashCode(key); 242 | for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = _entries[i].Next) 243 | { 244 | if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) return i; 245 | } 246 | } 247 | return -1; 248 | } 249 | 250 | #endregion Private 251 | } 252 | } -------------------------------------------------------------------------------- /DictionaryMini/DictionaryMini/DictionaryMini.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DictionaryMini/DictionaryMini/HashHelpersMini.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DictionaryMini 4 | { 5 | internal static class HashHelpersMini 6 | { 7 | //关于Buckets的长度为什么要为质数? 8 | //https://cs.stackexchange.com/questions/11029/why-is-it-best-to-use-a-prime-number-as-a-mod-in-a-hashing-function/64191#64191 9 | //Consider the set of keys K = { 0, 1,..., 100 } and a hash table where the number of buckets is m=12. Since 3 is a factor of 12, the keys that are multiples of 3 will be hashed to buckets that are multiples of 3: 10 | 11 | //Keys {0,12,24,36,...} 12 | //will be hashed to bucket 0. 13 | //Keys {3,15,27,39,...} 14 | //will be hashed to bucket 3. 15 | //Keys {6,18,30,42,...} will be hashed to bucket 6. 16 | //Keys {9,21,33,45,...} will be hashed to bucket 9. 17 | //If K is uniformly distributed(i.e., every key in K is equally likely to occur), then the choice of m is not so critical.But, what happens if K is not uniformly distributed? Imagine that the keys that are most likely to occur are the multiples of 3. In this case, all of the buckets that are not multiples of 3 will be empty with high probability (which is really bad in terms of hash table performance). 18 | 19 | //This situation is more common that it may seem. Imagine, for instance, that you are keeping track of objects based on where they are stored in memory. If your computer's word size is four bytes, then you will be hashing keys that are multiples of 4. Needless to say that choosing m to be a multiple of 4 would be a terrible choice: you would have 3m/4 buckets completely empty, and all of your keys colliding in the remaining m/4 buckets. 20 | 21 | // In general: 22 | 23 | //Every key in K that shares a common factor with the number of buckets m will be hashed to a bucket that is a multiple of this factor. 24 | 25 | //Therefore, to minimize collisions, it is important to reduce the number of common factors between m and the elements of K. How can this be achieved? By choosing m to be a number that has very few factors: a prime number. 26 | 27 | private static readonly int[] Primes = { 28 | 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 29 | 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 30 | 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 31 | 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 32 | 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; 33 | 34 | public static bool IsPrime(int candidate) 35 | { 36 | if ((candidate & 1) != 0) 37 | { 38 | int limit = (int)Math.Sqrt(candidate); 39 | for (int divisor = 3; divisor <= limit; divisor += 2) 40 | { 41 | if ((candidate % divisor) == 0) 42 | return false; 43 | } 44 | return true; 45 | } 46 | return (candidate == 2); 47 | } 48 | 49 | public static int GetPrime(int min) 50 | { 51 | if (min < 0) 52 | throw new ArgumentOutOfRangeException(); 53 | for (int i = 0; i < Primes.Length; i++) 54 | { 55 | int prime = Primes[i]; 56 | if (prime >= min) return prime; 57 | } 58 | for (int i = (min | 1); i < Int32.MaxValue; i += 2) 59 | { 60 | if (IsPrime(i)) 61 | return i; 62 | } 63 | return min; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /DictionaryMini/Example/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DictionaryMini/Example/Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9E7B4963-4409-4032-BFC5-BC76EC2D749D} 8 | Exe 9 | Example 10 | Example 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {6ea44f47-93a8-431b-8c9e-2d9356e01602} 55 | DictionaryMini 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /DictionaryMini/Example/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using DictionaryMini; 5 | 6 | namespace Example 7 | { 8 | internal class Program 9 | { 10 | private static ConcurrentDictionaryMini _concurrentDictionaryMini = new ConcurrentDictionaryMini(); 11 | private static Random _rd = new Random(); 12 | 13 | private static void Main() 14 | { 15 | Console.WriteLine("Demo of diction mini"); 16 | DemoDictionMini(); 17 | 18 | Console.WriteLine("Demo of ConcurrentDictionaryMini"); 19 | DemoConcurrentDictionaryMini(); 20 | } 21 | 22 | private static void DemoDictionMini() 23 | { 24 | var dicMini = new DictionaryMini(5); 25 | dicMini.Add(1, "liu ZhenYu"); 26 | dicMini.Add(new KeyValuePair(2, "coder ayu")); 27 | 28 | Console.WriteLine(dicMini[1]); 29 | Console.WriteLine(dicMini[2]); 30 | 31 | dicMini.Remove(1); 32 | Console.WriteLine(dicMini.ContainsKey(1)); 33 | 34 | dicMini.Clear(); 35 | Console.WriteLine(dicMini.ContainsKey(2)); 36 | } 37 | 38 | private static void DemoConcurrentDictionaryMini() 39 | { 40 | var threadList = new List(); 41 | for (int i = 0; i < 4; i++) 42 | { 43 | threadList.Add(new Thread(WriteMethod)); 44 | threadList.Add(new Thread(ReadMethod)); 45 | threadList.Add(new Thread(RemoveMethod)); 46 | } 47 | threadList.ForEach(u => u.Start()); 48 | 49 | Thread.Sleep(1000); 50 | 51 | threadList.ForEach(u => u.Abort()); 52 | 53 | Console.ReadLine(); 54 | } 55 | 56 | private static void WriteMethod() 57 | { 58 | while (true) 59 | { 60 | var index = GetRandomIndex(); 61 | var value = DateTime.Now; 62 | if (_concurrentDictionaryMini.TryAdd(index, value)) 63 | { 64 | Console.WriteLine($"Success Add,key:{index},value:{value}"); 65 | } 66 | else 67 | { 68 | Console.WriteLine($"Fail Add,key:{index},value:{value}"); 69 | } 70 | } 71 | } 72 | 73 | private static void ReadMethod() 74 | { 75 | while (true) 76 | { 77 | var index = GetRandomIndex(); 78 | if (_concurrentDictionaryMini.TryGetValue(index, out DateTime value)) 79 | { 80 | Console.WriteLine($"Success Get,key:{index},value:{value}"); 81 | } 82 | else 83 | { 84 | Console.WriteLine($"Fail Get,key:{index}"); 85 | } 86 | } 87 | } 88 | 89 | private static void RemoveMethod() 90 | { 91 | while (true) 92 | { 93 | var index = GetRandomIndex(); 94 | if (_concurrentDictionaryMini.TryRemove(index, out DateTime value)) 95 | { 96 | Console.WriteLine($"Success Remove,key:{index},value:{value}"); 97 | } 98 | else 99 | { 100 | Console.WriteLine($"Fail Remove,key:{index}"); 101 | } 102 | } 103 | } 104 | 105 | private static int GetRandomIndex() 106 | { 107 | return _rd.Next(0, 10); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /DictionaryMini/Example/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Example")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Example")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9e7b4963-4409-4032-bfc5-bc76ec2d749d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zhenyu liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Pic/ConsurrentDictionary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonLiuLiuLiuLiu/DictionaryMini/b7529cfd1f854933311a0d1e8bf78345f10f46a4/Pic/ConsurrentDictionary.png -------------------------------------------------------------------------------- /Pic/HashFunction.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Fox 23 | The red fox 24 | runs across 25 | the ice 26 | The red fox 27 | walks across 28 | the ice 29 | Hash 30 | function 31 | Hash 32 | function 33 | Hash 34 | function 35 | DFCD3454 36 | 52ED879E 37 | 46042841 38 | Input 39 | Hash sum 40 | 41 | -------------------------------------------------------------------------------- /Pic/chain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonLiuLiuLiuLiu/DictionaryMini/b7529cfd1f854933311a0d1e8bf78345f10f46a4/Pic/chain.gif -------------------------------------------------------------------------------- /Pic/hashtable0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | hash 85 | function 86 | 87 | 88 | 89 | keys 90 | 91 | 92 | 93 | 94 | John Smith 95 | 96 | 97 | 98 | 99 | 100 | 101 | Lisa Smith 102 | 103 | 104 | 105 | 106 | 107 | 108 | Sandra Dee 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | buckets 121 | 122 | 123 | 124 | 125 | 00 126 | 127 | 128 | 129 | 130 | 01 131 | 132 | 521-8976 133 | 134 | 135 | 136 | 02 137 | 138 | 521-1234 139 | 140 | 141 | 142 | 03 143 | 144 | 145 | 146 | : 147 | : 148 | 149 | 150 | 151 | 13 152 | 153 | 154 | 155 | 156 | 14 157 | 158 | 521-9655 159 | 160 | 161 | 162 | 15 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /Pic/hashtable1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | keys 85 | 86 | 87 | 88 | 89 | John Smith 90 | 91 | 92 | 93 | 94 | 95 | 96 | Lisa Smith 97 | 98 | 99 | 100 | 101 | 102 | 103 | Sam Doe 104 | 105 | 106 | 107 | 108 | 109 | 110 | Sandra Dee 111 | 112 | 113 | 114 | 115 | 116 | 117 | Ted Baker 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | buckets 130 | 131 | 132 | 133 | 134 | 000 135 | 136 | 137 | 138 | 139 | 140 | 001 141 | 142 | 143 | 144 | 145 | 146 | 002 147 | 148 | 149 | 150 | 151 | : 152 | : 153 | 154 | 155 | 156 | 151 157 | 158 | 159 | 160 | 161 | 162 | 152 163 | 164 | 165 | 166 | 167 | 168 | 153 169 | 170 | 171 | 172 | 173 | 174 | 154 175 | 176 | 177 | 178 | 179 | : 180 | : 181 | 182 | 183 | 184 | 253 185 | 186 | 187 | 188 | 189 | 190 | 254 191 | 192 | 193 | 194 | 195 | 196 | 255 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | entries 208 | 209 | 210 | 211 | 212 | 213 | 214 | Lisa Smith 215 | 216 | 521-8976 217 | 218 | 219 | 220 | 221 | 222 | John Smith 223 | 224 | 521-1234 225 | 226 | 227 | 228 | 229 | 230 | Sandra Dee 231 | 232 | 521-9655 233 | 234 | 235 | 236 | 237 | 238 | Ted Baker 239 | 240 | 418-4165 241 | 242 | 243 | 244 | 245 | 246 | Sam Doe 247 | 248 | 521-5030 249 | 250 | 251 | 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /Pic/hashtable2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | keys 85 | 86 | 87 | 88 | 89 | John Smith 90 | 91 | 92 | 93 | 94 | 95 | 96 | Lisa Smith 97 | 98 | 99 | 100 | 101 | 102 | 103 | Sam Doe 104 | 105 | 106 | 107 | 108 | 109 | 110 | Sandra Dee 111 | 112 | 113 | 114 | 115 | 116 | 117 | Ted Baker 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | buckets 130 | 131 | 132 | 133 | 134 | 000 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 001 143 | 144 | Lisa Smith 145 | 146 | 521-8976 147 | 148 | 149 | 150 | 151 | 152 | 002 153 | 154 | 155 | 156 | 157 | 158 | 159 | : 160 | : 161 | : 162 | : 163 | 164 | 165 | 166 | 151 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 152 175 | 176 | John Smith 177 | 178 | 521-1234 179 | 180 | 181 | 182 | 183 | 184 | 153 185 | 186 | Ted Baker 187 | 188 | 418-4165 189 | 190 | 191 | 192 | 193 | 194 | 154 195 | 196 | 197 | 198 | 199 | 200 | 201 | : 202 | : 203 | : 204 | : 205 | 206 | 207 | 208 | 253 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 254 217 | 218 | Sam Doe 219 | 220 | 521-5030 221 | 222 | 223 | 224 | 225 | 226 | 255 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | overflow 240 | 241 | 242 | entries 243 | 244 | 245 | 246 | 247 | 248 | 249 | Sandra Dee 250 | 251 | 521-9655 252 | 253 | 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /Pic/hashtable3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | keys 85 | 86 | 87 | 88 | 89 | John Smith 90 | 91 | 92 | 93 | 94 | 95 | 96 | Lisa Smith 97 | 98 | 99 | 100 | 101 | 102 | 103 | Sam Doe 104 | 105 | 106 | 107 | 108 | 109 | 110 | Sandra Dee 111 | 112 | 113 | 114 | 115 | 116 | 117 | Ted Baker 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | buckets 130 | 131 | 132 | 133 | 134 | 000 135 | 136 | 137 | 138 | 139 | 140 | 001 141 | 142 | Lisa Smith 143 | 144 | 521-8976 145 | 146 | 147 | 148 | 002 149 | 150 | 151 | 152 | 153 | : 154 | : 155 | : 156 | 157 | 158 | 159 | 151 160 | 161 | 162 | 163 | 164 | 165 | 152 166 | 167 | John Smith 168 | 169 | 521-1234 170 | 171 | 172 | 173 | 153 174 | 175 | Sandra Dee 176 | 177 | 521-9655 178 | 179 | 180 | 181 | 154 182 | 183 | Ted Baker 184 | 185 | 418-4165 186 | 187 | 188 | 189 | 155 190 | 191 | 192 | 193 | 194 | : 195 | : 196 | : 197 | 198 | 199 | 200 | 253 201 | 202 | 203 | 204 | 205 | 206 | 254 207 | 208 | Sam Doe 209 | 210 | 521-5030 211 | 212 | 213 | 214 | 255 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 一套帮助你理解Dictionary的精简代码 2 | 3 | [Dictionary](https://github.com/liuzhenyulive/DictionaryMini/blob/master/DictionaryMini.md) 4 | [ConcurrentDictionaryMini(并发字典)](https://github.com/liuzhenyulive/DictionaryMini/blob/master/ConcurrentDictionaryMini.md) --------------------------------------------------------------------------------