最近学习相关集合知识时,发现微软对集合使用的建议,建议新的代码中使用泛型集合(Dictionary< T, T >,List< T >等),不要使用非泛型集合(HashTable,ArrayList等),原因有两点:非泛型集合容易出错(编译器不进行类型检查);非泛型集合性能较差,实践出真理,咱们对HashTable 和 Dictionary<T,T>集合性能进行验证。
微软建议非泛型集合不要再使用
DE0006: Non-generic collections shouldn’t be used
原因
在 .net 创建初始,泛型数据类型还不存在,这也是 System.Collections 命名空间中没有泛型类型的原因。后来,在 System.Collections.Generic 和 System.Collections.ObjectModel 命名空间中引入了泛型数据类型。
建议
对于新代码,你不应该使用非泛型集合,原因如下:
- 容易出错:由于非泛型集合是无类型的,它需要频繁地在对象(Object)和实际类型之间进行强制转换。由于编译器不能检查类型是否一致,所以更容易将错误的类型放入集合中。
- 性能较差:泛型集合的优点是值类型不必进行对象装箱。例如,List 将其数据存储在 int[] 数组中。这比将数据存储在 object[] 中要好得多,因为后者需要装箱。
非泛型集合替换泛型集合对照表
下表显示了如何将非泛型集合类型替换为 System.Collections.Generic 或System.Collections.ObjectModel 名称空间中的泛型类型:
代码验证
using System.Collections;
using System.Diagnostics;
//运行次数
int _runTimes = 10;
//集合元素个数
int _elementCont = 10000;
long[] _htAddRunTime = new long[_runTimes];
long[] _htGetRunTime = new long[_runTimes];
long[] _dictAddRunTime = new long[_runTimes];
long[] _dictGetRunTime = new long[_runTimes];
Stopwatch sw = new Stopwatch();
//ElapsedTicks 一秒钟tick数,一秒10000000次,即1毫秒10000次
//每个电脑不同
Console.WriteLine($"当前电脑Frequency:{Stopwatch.Frequency}");
Console.WriteLine($"一毫秒 = {Stopwatch.Frequency/1000} tick");
Console.WriteLine("");
for (int j = 0; j < _runTimes; ++j)
{
sw.Restart();
Hashtable _ht = new Hashtable();
for (int i = 0; i < _elementCont; i++)
{
_ht.Add(i, i);
}
sw.Stop();
_htAddRunTime[j] = sw.ElapsedTicks;
sw.Restart();
for (int i = 0; i < _elementCont; i++)
{
int temp = (int)_ht[i];
}
sw.Stop();
_htGetRunTime[j] = sw.ElapsedTicks;
sw.Restart();
Dictionary<int, int> _dict = new Dictionary<int, int>();
for (int i = 0; i < _elementCont; i++)
{
_dict.Add(i, i);
}
sw.Stop();
_dictAddRunTime[j] = sw.ElapsedTicks;
sw.Restart();
for (int i = 0; i < _elementCont; i++)
{
int temp = _dict[i];
}
sw.Stop();
_dictGetRunTime[j] = sw.ElapsedTicks;
}
const int _timeAlignment = 7;
const int _hintAlignment = 13;
Console.WriteLine($"增加{_elementCont}个元素循环{_runTimes}次耗时情况如下:");
Console.Write($"{"Hashtable元素:",_hintAlignment}");
long _htTotalAddTime = 0;
foreach (long _time in _htAddRunTime)
{
Console.Write($"{_time,_timeAlignment}");
_htTotalAddTime += _time;
}
Console.WriteLine($"");
Console.Write($"{"Dictionary元素:",_hintAlignment}");
long _dictTotalAddTime = 0;
foreach (long _time in _dictAddRunTime)
{
Console.Write($"{_time,_timeAlignment}");
_dictTotalAddTime += _time;
}
Console.WriteLine($"");
Console.WriteLine($"");
Console.WriteLine($"Hashtable元素平均耗时:{_htTotalAddTime / _runTimes} ");
Console.WriteLine($"Dictionary元素平均耗时:{_dictTotalAddTime / _runTimes} ");
Console.WriteLine($"");
Console.WriteLine($"");
Console.WriteLine($"获取{_elementCont}个元素循环{_runTimes}次耗时情况如下:");
Console.Write($"{"Hashtable元素:",_hintAlignment}");
long _htTotalGetTime = 0;
foreach (long _time in _htGetRunTime)
{
Console.Write($"{_time,_timeAlignment}");
_htTotalGetTime += _time;
}
Console.WriteLine($"");
Console.Write($"{"Dictionary元素:",_hintAlignment}");
long _dictTotalGetTime = 0;
foreach (long _time in _dictGetRunTime)
{
Console.Write($"{_time,_timeAlignment}");
_dictTotalGetTime += _time;
}
Console.WriteLine($"");
Console.WriteLine($"");
Console.WriteLine($"Hashtable元素平均耗时:{_htTotalGetTime / _runTimes} ");
Console.WriteLine($"Dictionary元素平均耗时:{_dictTotalGetTime / _runTimes} ");
注意:
ElapsedTicks没有明确的单位,是由Stopwatch.Frequency确定的。Stopwatch.Frequency表示秒表的频率(一秒中滴答的次数)。Stopwatch.Frequency我认为与电脑时钟频率有关(未确认,但在不同电脑实验过,确实根据电脑配置不同,数值确实不一样)。
添加10个数值型元素,并且循环添加10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 145 | 6 | 1 | 23 | 0 | 1 | 18 | 1 | 1 | 1 | 19 |
Dictionary元素 | 4409 | 5 | 3 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 442 |
获取10个数值型元素,并且循环获取10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 15 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
Dictionary元素 | 1517 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 151 |
添加100个数值型元素,并且循环添加10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 151 | 25 | 32 | 40 | 24 | 23 | 24 | 18 | 25 | 27 | 38 |
Dictionary元素 | 1748 | 18 | 46 | 11 | 10 | 10 | 11 | 5 | 11 | 10 | 188 |
获取100个数值型元素,并且循环获取10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 39 | 8 | 6 | 8 | 7 | 7 | 13 | 14 | 6 | 8 | 11 |
Dictionary元素 | 949 | 3 | 2 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 97 |
添加1000个数值型元素,并且循环添加10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 294 | 172 | 171 | 175 | 224 | 174 | 167 | 181 | 199 | 180 | 193 |
Dictionary元素 | 1732 | 93 | 80 | 102 | 95 | 92 | 80 | 90 | 86 | 100 | 255 |
获取1000个数值型元素,并且循环获取10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 76 | 64 | 64 | 63 | 67 | 59 | 62 | 62 | 63 | 88 | 66 |
Dictionary元素 | 1046 | 18 | 18 | 20 | 18 | 18 | 18 | 18 | 17 | 28 | 121 |
添加10000个数值型元素,并且循环添加10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 1785 | 3819 | 4454 | 2313 | 901 | 923 | 1945 | 833 | 802 | 4735 | 2251 |
Dictionary元素 | 2748 | 1082 | 10018 | 418 | 386 | 441 | 318 | 321 | 666 | 772 | 1717 |
获取10000个数值型元素,并且循环获取10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 917 | 2339 | 2792 | 522 | 502 | 499 | 507 | 502 | 918 | 1460 | 1095 |
Dictionary元素 | 2208 | 594 | 185 | 182 | 180 | 180 | 180 | 180 | 349 | 257 | 449 |
添加100000个数值型元素,并且循环添加10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 70195 | 28199 | 61876 | 28444 | 29173 | 30757 | 44968 | 65196 | 44063 | 27328 | 43019 |
Dictionary元素 | 40772 | 40954 | 8566 | 17370 | 8723 | 32562 | 6410 | 10574 | 6841 | 4011 | 17678 |
获取100000个数值型元素,并且循环获取10次,运行时间如下:
元素类型 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第6次 | 第7次 | 第8次 | 第9次 | 第10次 | 平均值 |
---|---|---|---|---|---|---|---|---|---|---|---|
HashTable元素 | 6508 | 75286 | 29557 | 16925 | 13984 | 11272 | 32513 | 8586 | 29297 | 24270 | 24819 |
Dictionary元素 | 31133 | 30070 | 1980 | 1819 | 1876 | 4815 | 3230 | 2485 | 2477 | 1359 | 8124 |
总结
每次运行程序的第一次添加和获取元素,耗时都比较高(原因不明,不吝赐教),但是不影响咋们分析 HashTable 和 Dictionary<T,T>性能。
不管从横向比较,还是平均值比较,以及第一次的纵向比较,很容易得出以下结论:
- 在100个元素以下,对于数值型元素,HashTable性能明显优于Dictionary<T,T>。
- 在1000个元素左右,对于数值型元素,创建第一个对象时,HashTable性能优于Dictionary<T,T>。
- 在10000个元素以上,对于数值型元素,Dictionary<T,T>性能优于HashTable。
- 对应数值型元素,1000个元素是个分界线。
针对对象类型的元素就不在此文展示了,大家有兴趣可以改写以上代码,很容易实现,这里直接说结论:
- 1000个元素以下,对于Object类型元素,HashTable获取元素性能明显优于Dictionary<T,T>,添加元素性能不佳。
- 10000个元素以上,对于Object类型元素,Dictionary<T,T>获取元素性能优于HashTable。
当然,以上是研究性结论,对应现实程序代码意义不大,因为当元素的数据达到10000个时,才出现1-2毫秒的耗时,但是元素超过10000个时,Dictionary性能已超过HashTable,所以微软的建议,我认为完全可以采纳(哈哈)。