posts - 156, comments - 484, trackbacks - 5, articles - 25
   :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

在应用泛型中,我们经常使用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;
}

}
}



}

 

成长,我们一起见证!

Feedback

#1楼  回复 引用 查看   

2011-09-30 17:10 by Treenew Lyn      
pList.Add(dic[id]);

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

#2楼  回复 引用 查看   

2011-09-30 17:15 by easycode      
var list = from temp in dic select temp.Value;
pList = list as List<Person>;
list为表达式 此时pList为null
试试list.ToList()

#3楼  回复 引用 查看   

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

#4楼  回复 引用 查看   

2011-09-30 17:27 by yuejianjun      
Linq查询 方式时间不变
这个有奸情

#5楼  回复 引用 查看   

2011-09-30 17:29 by Treenew Lyn      
我测试的结果是:

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


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

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

#6楼  回复 引用 查看   

2011-09-30 17:31 by Treenew Lyn      
我修改后的测试代码
        /// <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

        }

#7楼  回复 引用 查看   

2011-09-30 17:32 by 诺贝尔      
linq只是延迟查询而已,如果真的要使用数据,效率一定更加低的。

#8楼[楼主]  回复 引用 查看   

2011-09-30 19:06 by 停留的风      
@Treenew Lyn
多谢指点。
上文linq得到的list然后as List<Person>;
结果确实为Null。

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

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

#9楼  回复 引用 查看   

2011-09-30 20:43 by .Net小菜菜      
pList = list as List<Person>;//此处为Null,有待商榷

linq是延迟查询的 拜托

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

#10楼[楼主]  回复 引用 查看   

2011-09-30 22:56 by 停留的风      
引用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