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
* *
* */
* * =============================================
* * 数据 | 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;
}
}
}
}
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()
pList = list as List<Person>;
list为表达式 此时pList为null
试试list.ToList()
list as List<Person>;
是一个明显就是错的……
这个结果永远为 NULL 值。
并且你只是 Clear List,并没有 重新 创建一个 List。这样会导致后面测试的性能越高。因为容器大小已被撑开了
是一个明显就是错的……
这个结果永远为 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#已经说了。
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同样会消耗很大的时间
这样做性能肯定最差,是否还有更合理的办法呢?
多谢指点。
上文linq得到的list然后as List<Person>;
结果确实为Null。
您指出的:AddRange方法,确实效率比较高,
再次感谢。
但是
您说linq最慢,我看了您的代码,在执行查询之后,又调用了ToList方法,
这个方法和单独的ToList同样会消耗很大的时间
这样做性能肯定最差,是否还有更合理的办法呢?
pList = list as List<Person>;//此处为Null,有待商榷
linq是延迟查询的 拜托
这里根本就没有执行
当然一直是4毫秒哟
linq是延迟查询的 拜托
这里根本就没有执行
当然一直是4毫秒哟
针对此说法专门写了测试用例,方法性能差别不大。
测试结果:
/*
* * -----------------------------------------------------
* * 循环赋值(索引器) | 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