话说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 >= minreturn 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;
        }

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

3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
            1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
            17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
            187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
            1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369

取得的时候有一个规则。比如未设置大小,第一次添加的时候,一定是取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,37,89,197,431,919,1931,4049,8419,17519,36353,75431。意思是不设置初始大小的情况,第一个必然取3,再从上面的表中取比这个数的两倍大的最小值,依次类推。那么,存取5万个键值对,实际分配大小是75431个空间。而预先设置大小,它会在上表中找比设置大小大的数的最小值。所以存5万个,设置5万初始大小的话,那么实际分配52361个空间,觉得不保险,设置6万,则分配62851个空间。(2008年7月31日 9:07:39 修正。)

2008年7月31日 by yurow @http://www.cnblogs.com/birdshover/

原文地址:https://www.cnblogs.com/birdshover/p/1256915.html