架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

话说List,Dictionary初始化大小 一、List

Posted on 2008-09-15 20:38  chen eric  阅读(1430)  评论(0编辑  收藏  举报
话说List,Dictionary初始化大小 一、List<T>

List
<T>也就是泛型集合。看它的大小分配方式,要看两段代码

 

1         private void EnsureCapacity(int min) 
2             if (_items.Length < min) {
3                 int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
4                 if (newCapacity < min) newCapacity = min;
5                 Capacity = newCapacity; 
6             }

7         }
 
 

 

        
public int Capacity 
            
get return _items.Length; }
            
set {
                
if (value != _items.Length) {
                    
if (value < _size) 
                        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
                    }
 
 
                    
if (value > 0{
                        T[] newItems 
= new T[value]; 
                        
if (_size > 0{
                            Array.Copy(_items, 
0, newItems, 0, _size);
                        }

                        _items 
= newItems; 
                    }

                    
else 
                        _items 
= _emptyArray; 
                    }

                }
 
            }

        }

 

 

系统默认分配的增长量是

private const int _defaultCapacity = 4

 

所以,假设不设置List的默认大小。即默认为0,那么在类初始化的时候,数组分配大小是0.如下代码

 

        
static T[]  _emptyArray = new T[0];
 
        
// Constructs a List. The list is initially empty and has a capacity 
        
// of zero. Upon adding the first element to the list the capacity is
        
// increased to 16, and then increased in multiples of two as required. 
        public List() {
            _items 
= _emptyArray;
        }

 

那么当第一次调用Add方法的时候,系统就会为List分配4个位置的大小。而超过4个则分配8个,超过8个就分配16个。也就是说,假设你添加了2049个值进去,那么实际分配的空间大小就是4096。分配5万个进去,就会分配65536个。额外多出来不少。而假如能自己判断出要添加的大概数量的话,那最好是预先分配大小了。预先分配大小,分配多少就是多少个。预先分配的大小一定要大于等于加进去的元素数量。否则,说不定比不分配更加糟糕。

 

        
public List(int capacity) 
            
if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
            _items 
= new T[capacity];
        }

 

 

 二、Dictionary
<TKey,TValue>

要是用List分配空间的方式来理解Dictionary,那就又错了。Dictionary有它自己的分配方式。

 

        
private void Initialize(int capacity) {
            
int size = HashHelpers.GetPrime(capacity);
            buckets 
= new int[size];
            
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
            entries 
= new Entry[size];
            freeList 
= -1
        }

 

 

        
private void Resize() {
            
int newSize = HashHelpers.GetPrime(count * 2);
            
int[] newBuckets = new int[newSize];
            
for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1
            Entry[] newEntries 
= new Entry[newSize];
            Array.Copy(entries, 
0, newEntries, 0, count); 
            
for (int i = 0; i < count; i++
                
int bucket = newEntries[i].hashCode % newSize;
                newEntries[i].next 
= newBuckets[bucket]; 
                newBuckets[bucket] 
= i;
            }

            buckets 
= newBuckets;
            entries 
= newEntries; 
        }

 

 

 

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        
internal static int GetPrime(int min)
        
{
            
if (min < 0
                
throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow"));
 
            
for (int i = 0; i < primes.Length; i++
            
{
                
int prime = primes[i]; 
                
if (prime >= min) return prime;
            }


            
//outside of our predefined table. 
            
//compute the hard way.
            for (int i = (min | 1); i < Int32.MaxValue;i+=2
            

                
if (IsPrime(i))
                    
return i; 
            }

            
return min;
        }

 

 

 

假设未设置大小,那么它会从下面的数据中取得。

37111723293747597189107131163197239293353431521631761919
            
110313271597193123332801337140494861583970138419101031214314591
            
17519210232522930293363534362752361628517543190523108631130363156437,
            
18775122530727037132444938935746723756068967282780740396889711626871395263
            
167431920091912411033289324934718994166287499955959994717199369

取得的时候有一个规则。比如未设置大小,第一次添加的时候,一定是取3个大小为初始值了。而到4个的时候,它会先取3的倍数6,而6小于7,那么它就取7。当第8个的时候,因为7的倍数是14,比11大,所以它就取17了。所以,假设存入50000个左右的key,到第467237 
+ 1个的时候,根据计算 75431 < 43627* 2 = 87254 < 90523,所以,它会为你分配90523个键值。浪费了吧,呵呵,而且在分配完成后,它会用循环的方式重新设置初始值。浪费几万次循环,实在可耻。

 

所以,如果判定存入的key的数量为50000左右的话,那么直接设置50000,它会取52361个,如果觉得不保险,那么设置成60000,就会分配62851。

 

sorry,上面计算有误,当5万个的时候,先取3,再取7,接着是17,
3789197431919193140498419175193635375431。意思是不设置初始大小的情况,第一个必然取3,再从上面的表中取比这个数的两倍大的最小值,依次类推。那么,存取5万个键值对,实际分配大小是75431个空间。而预先设置大小,它会在上表中找比设置大小大的数的最小值。所以存5万个,设置5万初始大小的话,那么实际分配52361个空间,觉得不保险,设置6万,则分配62851个空间。(2008年7月31日 9:07:39 修正。)