NET中dictionary的一个小坑

问题描述:前段时间做个东西,打算在dictionary上按顺序扫描,不复合key条件的元素移动到末尾,然后减少计数,计算下 一个元素。目的就是一遍扫描实现对key的分组处理,减少遍历次数。然而程序表现与预想中不一致,表现

                 出来 就 是移除再插入的顺序和处理前的顺序一致。

                复现后的场景:

            095332_xv3X_2403989.png

                  移除顺序对添加的顺序有影响。

原因:

        产生这个问题的原因在于Dictionary内部的实现机制,简单来说dictionary中维护了一个bucket数组和entry数组,

       前者用于key的hash定位,后者负责数据存储,一个freelist维护了一个当前空位入口。至于细节请参考这篇博客

         http://www.cnblogs.com/1-2-3/archive/2010/10/25/generic-dictionary-source-part2.html

        查看源码中移除代码:

        public bool Remove(TKey key) {           
         if(key == null) {               
          ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
            } 
            if (buckets != null) {           
                 int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;          
                 int bucket = hashCode % buckets.Length;       
                 int last = -1;               
                 for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next)
                 {                    
                 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {                            if (last < 0) {                           
                              buckets[bucket] = entries[i].next;
                        }                      
                          else {                     
                              entries[last].next = entries[i].next;
                        }                       
                         entries[i].hashCode = -1;              
                         entries[i].next = freeList;         
                         entries[i].key = default(TKey);                        
                         entries[i].value = default(TValue);                        
                         freeList = i;                        
                         freeCount++;                        
                         version++;                        
                         return true;
                    }
                }
            }            
            return false;
        }

         字典每次移除都会清空当前entry并将entry并入空闲索引链接中,指针指向上一个空位,当前entry作为索引链接的入口。

Add的方法源码截取:

            int index;            
            if (freeCount > 0) 
            {                
            index = freeList;               
            freeList = entries[index].next;  
            freeCount--;
            }            
            else {                
            if (count == entries.Length)
            {                    
            Resize();                    
            targetBucket = hashCode % buckets.Length;
            }                
                index = count;                
                count++;
            } 
            entries[index].hashCode = hashCode;            
            entries[index].next = buckets[targetBucket];            
            entries[index].key = key;            
            entries[index].value = value;            
            buckets[targetBucket] = index;            
            version++;

代码中首先需要获取到空闲位置,index=freeList中获取了最近移除的位置。后边对这个entry进行赋值了。

在看dictionary中获取顺序的代码截取:

  while ((uint)index < (uint)dictionary.count) 
  {                    
      if (dictionary.entries[index].hashCode >= 0) 
      {                        
           current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value);                        
            index++;                        
            return true;
       }
   }

位置是在enrties块上面进行顺序读取的。

分析:

       结合三段代码分析,当移除一个键的时候,当前块会成为空白块的入口,再次添加键的时候会重新占据这个位置。这可以解释我碰到的死循环问题。

      再来解释复现的情况:

104526_Exwo_2403989.png

     图解中可以看出这种情况的原因。结果符合预期的。dictionary中的顺序是不可信任的,如果一定要使用,需要考虑全面些。

转载于:https://my.oschina.net/hunjixin/blog/668531

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在C#创建一个Dictionary(字典),你可以使用Dictionary<TKey, TValue>类。这个类允许你将一个键(key)与一个值(value)相关联,类似于一个实际的字典,其键是唯一的,而值可以重复。下面是创建Dictionary的步骤: 1. 首先,你需要引入System.Collections.Generic命名空间,因为Dictionary类在这个命名空间定义。 2. 然后,声明一个Dictionary变量并实例化它。你需要指定键的类型(TKey)和值的类型(TValue)。例如,要创建一个键为字符串(string)类型,值为整数(int)类型的Dictionary,可以使用以下代码: ```csharp Dictionary<string, int> myDictionary = new Dictionary<string, int>(); ``` 3. 现在你可以向Dictionary添加键值对。使用Add()方法,将键和值作为参数传递给它。例如,将键为"apple",值为5的键值对添加到Dictionary的代码如下: ```csharp myDictionary.Add("apple", 5); ``` 4. 你也可以通过索引器(indexer)来访问和修改Dictionary的值。使用键作为索引来获取或设置对应的值。例如,要获取键为"apple"的值,可以使用以下代码: ```csharp int value = myDictionary["apple"]; ``` 如果键不存在,这将引发KeyNotFoundException异常。你可以使用ContainsKey()方法在访问之前检查键是否存在。 以上是创建和使用Dictionary的基本步骤。你可以根据需要添加、修改或删除键值对。请根据你的具体需求来使用Dictionary类的其他方法和属性。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [C#针对xml文件转化Dictionary的方法](https://download.csdn.net/download/weixin_38623272/12807380)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [重命名文件夹内所有文件的功能加入PNG转JPG 第三版](https://download.csdn.net/download/dearmite/88250580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值