这篇文章我来分析下对于泛型集合,采取不同的查询方式在性能上会有什么样的影响?
这里有一个城市简单信息的实体类:
- class CityInfo
- {
- public int CityID
- {
- get;
- set;
- }
- public string CityName
- {
- get;
- set;
- }
- public string CityNameEn
- {
- get;
- set;
- }
- public string CityAddress
- {
- get;
- set;
- }
- }
我们构造一个特别大的城市泛型类:
- int i = 100000;
- Random m = new Random();
- List<CityInfo> list = new List<CityInfo>();
- for (int j = 0; j < i; j++)
- {
- CityInfo info = new CityInfo();
- info.CityID = j;
- info.CityAddress = "aaaaaa" + j.ToString();
- info.CityName = "城市中文名称" + j.ToString();
- info.CityNameEn = "城市英文名称" + j.ToString();
- list.Add(info);
- }
根据城市ID查询某个城市的具体信息:
方法一:foreach:
- static CityInfo GetCityInfoByForeach(List<CityInfo> list, int CityID)
- {
- CityInfo info = new CityInfo();
- foreach (var item in list)
- {
- if (item.CityID == CityID)
- {
- info = item;
- breake;
- }
- }
- return info;
- }
方法二:for循环:
- static CityInfo GetCityInfoByFor(List<CityInfo> list, int CityID)
- {
- CityInfo info = new CityInfo();
- for (int i = 0; i < list.Count;i ++ )
- {
- if (list[i].CityID == CityID)
- {
- info = list [i];
- breake;
- }
- }
- return info;
- }
方法三:Linq查询:
- static CityInfo GetCityInfoByLinq(List<CityInfo> list,int CityID)
- {
- CityInfo info = new CityInfo();
- info = list.Where(p => p.CityID == CityID).FirstOrDefault();
- return info;
- }
然后随机产生一个城市ID,分别针对上面三种方式调用500次,这里何用老赵的CodeTimer来显示信息,执行结果如下:
- CodeTimer.Time("GetCityInfoByForeach", 500, () => GetCityInfoByForeach(list, m.Next(i - 1)));
- CodeTimer.Time("GetCityInfoByFor", 500, () => GetCityInfoByFor(list, m.Next(i - 1)));
- CodeTimer.Time("GetCityInfoByLinq", 500, () => GetCityInfoByLinq(list, m.Next(i - 1)));
性能从高到低表现为:for,foreach,linq,仔细查看三种方法生成IL代码,有一定的区别:
1:foreach方法在查询数据时,依赖了Enumerator ,它的特点是不能像对于数组一样使用索引,而只能将当前项指针移动到集合的第一个或下一个元素,这是它性能不是最优的主要问题所在。在这种方式中还有一个重要点就是在查询每个元素时都会有try finally块,这也是需要消耗部分性能的。
- .try
- {
- IL_000d: br.s IL_0022
- IL_000f: ldloca.s CS$5$0000
- IL_0011: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ConsoleApplication1.CityInfo>::get_Current()
- IL_0016: stloc.1
- IL_0017: ldloc.1
- IL_0018: callvirt instance int32 ConsoleApplication1.CityInfo::get_CityID()
- IL_001d: ldarg.1
- IL_001e: bne.un.s IL_0022
- IL_0020: ldloc.1
- IL_0021: stloc.0
- IL_0022: ldloca.s CS$5$0000
- IL_0024: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ConsoleApplication1.CityInfo>::MoveNext()
- IL_0029: brtrue.s IL_000f
- IL_002b: leave.s IL_003b
- } // end .try
- finally
- {
- IL_002d: ldloca.s CS$5$0000
- IL_002f: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ConsoleApplication1.CityInfo>
- IL_0035: callvirt instance void [mscorlib]System.IDisposable::Dispose()
- IL_003a: endfinally
- } // end handler
2:for方法在查询数据时,System.Collections.Generic.List`1<class ConsoleApplication1.CityInfo>::get_Item(int32),比起foreach少了try的处理,最重要的是能够使用索引访问元素。
3:linq方式性能最差。
非常感谢各位朋友的指点,特别是代码中忘记加break,现在是修改代码后的测试结果:结果和上面一样。
总结:泛型集合如果数据量大,最好采用for循环查询,数据量少的话,用linq方式最佳,代码优雅且简洁。
题外话:其实针对这种泛型集合查询,如果想优化性能,最好不要存储成泛型集合,采用Dictionary或者是hashtable效果更佳。