您当前的位置:KKBlog > 学无止境 > ASP.NET

Dictionary到List转换中的性能问题

在应用泛型中,我们经常使用Dictionary,经常会用到Dictionary到List的转换。

经过各位高人指点后,做出适当调整,以免误人子弟,特此对关注此帖的同仁深表感谢。希望能继续提醒、斧正。

Dictionary转换为List通常方法,可以有五种:

1、创建List的时候,将Dictionary的Value值作为参数

2、创建List后,调用List.AddRange方法

3、建立List,循环Dictionary逐个赋值

4、通过Linq查询,得到结果后调用ToList方法

5、用Dictionary对象自带的ToList方法

但是五种方法如何取舍呢?性能方面哪种更好一点呢?

针对此疑问,特做了测试验证。

测试结果如下:(经过多次测试,取平均值)

/*测试结果(时间为毫秒)       
             * * =============================================     
             * * 数据 | 10W | 100W | 1000W         
             * * --------------------------------------------- 
             * * 创建集合 | 2 | 28 | 280
             * * ---------------------------------------------
             * * AddRange | 19 | 33 | 362
             * * ---------------------------------------------
             * * 循环赋值 | 7 | 60 | 869         
             * * ---------------------------------------------           
             * * Linq查询 | 8 | 7 | 238 此没有相关性,只是作为下面ToList方法的参考 
             * * ---------------------------------------------
             * * Linq查询 
             * * 后ToList | 11 | 97 | 1627
             * * ---------------------------------------------    
             * * ToList方法 | 5 | 23 | 948        
             * *          
             * 
*/

通过上述结果,可以得出结论:

 结论1:方法1和方法2性能较好,可优先考虑方法1

 结论2:TOList方法性能方面稍差,方法4和方法5不可取。

 

具体测试用例代码如下,如有纰漏,请指出:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ToListTest
{
class Program
    {
static void Main(string[] args)
        {
            TestToList();
        }
/// <summary>
/// 测试代码
/// </summary>
private static void TestToList()
        {
            Dictionary<int, Person> dic = new Dictionary<int, Person>();
#region 填充数据
            Person p;
for (int i = 0; i < 100000; i++)
            {
                p = new Person(i, "P_" + i.ToString());
                dic.Add(i, p);
            }
#endregion

            List<Person> pList;
            Stopwatch watcher = new Stopwatch();

#region 创建集合对象时,将集合作为参数
            watcher.Reset();
            watcher.Start();

            pList = new List<Person>(dic.Values);
            Console.WriteLine("new List<>\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
#endregion

#region 调用方法AddRange

            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();

            pList.AddRange(dic.Values);

            Console.WriteLine("测试AddRange\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
#endregion

#region 测试循环赋值
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();

foreach (var item in dic)
            {
                pList.Add(item.Value);
            }
            Console.WriteLine("测试循环赋值\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
#endregion

#region 测试Linq

            watcher.Reset();
            watcher.Start();

//var list = from temp in dic
//           select temp.Value;

//pList = list as List<Person>; //pList 确实为Null,这样操作错误
//原因:
//得到的list为:{System.Linq.Enumerable.WhereSelectEnumerableIterator<KeyValuePair<int,Person>,Person>}

            IEnumerable<Person> list = from temp in dic
                                       select temp.Value;

            Console.WriteLine("测试Linq\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
#endregion

#region 测试Linq 需要ToList()

            watcher.Reset();
            watcher.Start();
            pList = (from temp in dic
                     select temp.Value).ToList();

            Console.WriteLine("测试Linq,需要ToList\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
#endregion

#region 测试ToList
            watcher.Reset();
            watcher.Start();
            pList = dic.Values.ToList<Person>();
            Console.WriteLine("测试ToList\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();


#endregion

/*测试结果(时间为毫秒)       
             * * =============================================     
             * * 数据 | 10W | 100W | 1000W         
             * * --------------------------------------------- 
             * * 创建集合 | 2 | 28 | 280
             * * ---------------------------------------------
             * * AddRange | 19 | 33 | 362
             * * ---------------------------------------------
             * * 循环赋值 | 7 | 60 | 869         
             * * ---------------------------------------------           
             * * Linq查询 | 8 | 7 | 238 此没有相关性,只是作为下面ToList方法的参考 
             * * ---------------------------------------------
             * * Linq查询 
             * * 后ToList | 11 | 97 | 1627
             * * ---------------------------------------------    
             * * ToList方法 | 5 | 23 | 948        
             * *          
             * 
*/

        }

class Person
        {
private int id;

public int ID
            {
get { return id; }
set { id = value; }
            }

private string name;

public string Name
            {
get { return name; }
set { name = value; }
            }

public Person(int id, string name)
            {
this.id = id;
this.name = name;
            }

        }
    }


}
 

pList.Add(dic[id]);

你知道这个性能有多影响吗

var list = from temp in dic select temp.Value;
pList = list as List<Person>;
list为表达式 此时pList为null
试试list.ToList()

list as List<Person>;
是一个明显就是错的……
这个结果永远为 NULL 值。
并且你只是 Clear List,并没有 重新 创建一个 List。这样会导致后面测试的性能越高。因为容器大小已被撑开了

Linq查询 方式时间不变
这个有奸情

我测试的结果是:

new List<>:105
测试AddRange:130
测试循环赋值:243
测试Linq:544
测试ToList:113


1、按照先后顺序,初始化是最快的。原因是初始化指定 List 容器大小。同时免去多次检查容器大小是否溢出。
2、而 ToList 的原理与1相同,但是由于是扩展方法,多了一些额外的性能损失。
3、AddRange也差不多, 是在原有的容器大小基础下,增加需要增加的大小,故而性能会比较好。
4、而循环赋值,在没有确保容器大小的时候,性能不好。
5、Linq,性能必然最差!这点毋庸置疑。

至于你的代码为什么会出现 LINQ 最快。我在3#已经说了。

我修改后的测试代码
        /// <summary>
        /// 测试代码
        /// </summary>
        private static void TestToList()
        {
            Dictionary<int, Person> dic = new Dictionary<int, Person>();
            #region 填充数据
            Person p;
            for(int i = 0 ; i < 10000000 ; i++)
            {
                p = new Person(i, "P_" + i.ToString());
                dic.Add(i, p);
            }
            #endregion
 
            List<Person> pList;
            Stopwatch watcher = new Stopwatch();
 
            #region AddRange
            watcher.Reset();
            watcher.Start();
 
            pList = new List<Person>(dic.Values);
            Console.WriteLine("new List<>\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
            #region AddRange
 
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();
 
            pList.AddRange(dic.Values);
 
            Console.WriteLine("测试AddRange\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
 
            #region 测试循环赋值
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();
 
            foreach(var item in dic)
            {
                pList.Add(item.Value);
            }
            Console.WriteLine("测试循环赋值\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
            pList.Clear();
 
            #region 测试Linq
 
            watcher.Reset();
            watcher.Start();
            pList = (from temp in dic
                        select temp.Value).ToList();
 
            Console.WriteLine("测试Linq\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
            #region 测试ToList
            watcher.Reset();
            watcher.Start();
            pList = dic.Values.ToList<Person>();
            Console.WriteLine("测试ToList\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
 
 
 
            #endregion
 
        }

linq只是延迟查询而已,如果真的要使用数据,效率一定更加低的。

@Treenew Lyn
多谢指点。
上文linq得到的list然后as List<Person>;
结果确实为Null。

您指出的:AddRange方法,确实效率比较高,
再次感谢。

但是
您说linq最慢,我看了您的代码,在执行查询之后,又调用了ToList方法,
这个方法和单独的ToList同样会消耗很大的时间
这样做性能肯定最差,是否还有更合理的办法呢?

pList = list as List<Person>;//此处为Null,有待商榷

linq是延迟查询的 拜托

这里根本就没有执行
当然一直是4毫秒哟

引用Treenew Lyn:
pList.Add(dic[id]);

你知道这个性能有多影响吗

针对此说法专门写了测试用例,方法性能差别不大。
测试结果:
/*
* * -----------------------------------------------------
* * 循环赋值(索引器) | 7 | 85 | 1086 887
* * ------------------------------------------------------
* * 循环赋值 (KeyValue) | 6 | 59 | 741 672
* * -----------------------------------------------------
* * 循环赋值(隐式类型var)| 5 | 66 | 989 1157
* * -----------------------------------------------------
* */

            #region 测试循环赋值 根据key获得
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();
 
            foreach (int id in dic.Keys)
            {
                pList.Add(dic[id]);
            }
            Console.WriteLine("测试循环赋值,索引器\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
            #region 测试循环赋值 KeyValuePair
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();
 
            foreach (KeyValuePair<int,Person> item in dic)
            {
                pList.Add(item.Value);
            }
            Console.WriteLine("测试循环赋值,KeyValuePair\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion
 
            #region 测试循环赋值 隐式类型 var
            pList = new List<Person>();
            watcher.Reset();
            watcher.Start();
 
            foreach (var item in dic)
            {
                pList.Add(item.Value);
            }
            Console.WriteLine("测试循环赋值,隐式类型 var\t{0}", watcher.ElapsedMilliseconds);
            watcher.Stop();
            #endregion

 
二维码
意见反馈 二维码