前言

我们知道,有时候在一些项目中,为了性能,往往会一次性加载很多条记录来进行循环处理(备注:而非列表呈现)。比如:从数据库中加载 10000 个用户,并且每个用户包含了 20  个“爱好”,在 WinForm 界面我们需要用进度条的方式实时显示每个用户处理的进度,这时候当然是一次性加载很多条记录来进行循环处理更加快速。如下图:

问题和解决方案

通常,我们会先 foreach 从数据库中加载的用户集合,然后从“爱好”集合中筛选出某个用户的所有爱好来进行处理。比如如下代码:

foreach (var userItem in userList)
{
List<Hobby> tempHobbyList = hobbyList.Where(c => c.UserId == userItem.Id).ToList();
foreach (var hobbyItem in tempHobbyList)
{
string hobbyName = hobbyItem.HobbyName;
//...其它的代码
}
}

如下图,我们编写了如上面的代码来进行实际测试,结果发现,当 userList 足够多、或者 hobbyList 足够多,或者 userList、hobbyList 同时都足够多时,性能会大大的降低,这时候就需要我们改变方式,采用先把 hobbyList 安装 userId 进行分组(GroupBy)变成 Dictionary<int, List<Hobby>>,然后在 userList 的循环体中直接从 Dictionary<int, List<Hobby>> 从检索,这样性能会大大提高。

实际测试

namespace ConsAppTest
{
class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
} class Hobby
{
public int HobbyId { get; set; }
public string HobbyName { get; set; }
public int UserId { get; set; }
} class Program
{
private const int UserCountForNew = ;
private const int HobbyCountForEveryUser = ; private static async Task Main(string[] args)
{
string messageTemplate = $"产生 { UserCountForNew } 个虚拟用户,并且每个虚拟用户产生了 { HobbyCountForEveryUser } 个“爱好”共耗时 {{0}} 秒。"; //下面是方案1 - 开始
Stopwatch globalWatch1 = Stopwatch.StartNew();
List<User> userList1;
List<Hobby> hobbyList1;
CreateUserListAndHobbyList(out userList1, out hobbyList1);
globalWatch1.Stop();
double totalSeconds1 = (double)globalWatch1.ElapsedMilliseconds / ;
Console.WriteLine(messageTemplate, totalSeconds1.ToString("#.##")); Stopwatch stage1Watch = Stopwatch.StartNew();
foreach (var userItem in userList1)
{
List<Hobby> tempHobbyList = hobbyList1.Where(c => c.UserId == userItem.Id).ToList();
foreach (var hobbyItem in tempHobbyList)
{
string hobbyName = hobbyItem.HobbyName;
}
}
stage1Watch.Stop();
double stage1WatchTotalSeconds = (double)stage1Watch.ElapsedMilliseconds / ;
Console.WriteLine("方案1总耗时:{0}\n\n", stage1WatchTotalSeconds.ToString("#.##"));
//下面是方案1 - 结束 //下面是方案2 - 开始
Stopwatch globalWatch2 = Stopwatch.StartNew();
List<User> userList2;
List<Hobby> hobbyList2;
CreateUserListAndHobbyList(out userList2, out hobbyList2);
globalWatch2.Stop();
double totalSeconds2 = (double)globalWatch2.ElapsedMilliseconds / ;
Console.WriteLine(messageTemplate, totalSeconds2.ToString("#.##")); Stopwatch stage2Watch = Stopwatch.StartNew();
foreach (var userItem in userList2)
{
List<Hobby> tempHobbyList = hobbyList2.Where(c => c.UserId == userItem.Id).ToList();
foreach (var hobbyItem in tempHobbyList)
{
string hobbyName = hobbyItem.HobbyName;
}
hobbyList2.RemoveAll(c => c.UserId == userItem.Id);
}
stage2Watch.Stop();
double stage2WatchTotalSeconds = (double)stage2Watch.ElapsedMilliseconds / ;
Console.WriteLine("方案2总耗时:{0}\n\n", stage2WatchTotalSeconds.ToString("#.##"));
//下面是方案2 - 结束 //下面是方案3 - 开始
Stopwatch globalWatch3 = Stopwatch.StartNew();
List<User> userList3;
List<Hobby> hobbyList3;
CreateUserListAndHobbyList(out userList3, out hobbyList3);
globalWatch3.Stop();
double totalSeconds3 = (double)globalWatch3.ElapsedMilliseconds / ;
Console.WriteLine(messageTemplate, totalSeconds3.ToString("#.##")); Stopwatch stage3Watch = Stopwatch.StartNew();
Dictionary<int, List<Hobby>> keyValuePairs = hobbyList3.GroupBy<Hobby, int>(c => c.UserId).ToDictionary(c => c.Key, c => c.ToList());
foreach (var userItem in userList3)
{
List<Hobby> tempHobbyList = keyValuePairs[userItem.Id];
foreach (var hobbyItem in tempHobbyList)
{
string hobbyName = hobbyItem.HobbyName;
}
}
stage3Watch.Stop();
double stage3WatchTotalSeconds = (double)stage3Watch.ElapsedMilliseconds / ;
Console.WriteLine("方案3总耗时:{0}\n\n", stage3WatchTotalSeconds.ToString("#.##")); //下面是方案3 - 结束 Console.ReadLine(); } private static void CreateUserListAndHobbyList(out List<User> rightUserList, out List<Hobby> rightHobbyList)
{
List<User> userList = new List<User>();
List<Hobby> hobbyList = new List<Hobby>();
int hobbyIndex = ;
for (int i = ; i <= UserCountForNew; i++)
{
int userId = i;
userList.Add(new User { Id = userId, Name = "张三" + userId, Age = userId });
for (int j = ; j < HobbyCountForEveryUser; j++)
{
hobbyIndex++;
hobbyList.Add(new Hobby
{
HobbyId = hobbyIndex,
HobbyName = "唱歌" + hobbyIndex,
UserId = userId
});
}
}
rightUserList = userList;
rightHobbyList = hobbyList;
} }
}

运行截图

结论

最终,我们看到,这三种方案,每种方案都产生 6000 个虚拟用户,并且每个虚拟用户产生了 20 个“爱好”。第一二种方案检索的速度要 8秒、9秒左右,而第三种方案从 Dictionary<int, List<Hobby>> 中检索只需要 0.01 到 0.02 秒,可谓是性能大大提升。

谢谢浏览!

实例演示 C# 中 Dictionary<Key, Value> 的检索速度远远大于 hobbyList.Where(c => c.UserId == user.Id)的更多相关文章

  1. vue.js(09)--v-for中的key

    v-for中key的使用注意事项 <!DOCTYPE html> <html lang="en"> <head> <meta charse ...

  2. ASP.NET中Dictionary基本用法实例分析

    本文实例讲述了ASP.NET中Dictionary基本用法.分享给大家供大家参考,具体如下: //Dictionary位于System.Collections.Generic命名空间之下 /*  * ...

  3. 最简实例演示asp.net5中用户认证和授权(3)

    上接: 最简实例演示asp.net5中用户认证和授权(2) 在实现了角色的各种管理接口后,下一步就是实现对用户的管理,对用户管理的接口相对多一些,必须要实现的有如下三个: 1 public inter ...

  4. 最简实例演示asp.net5中用户认证和授权(2)

    上接最简实例演示asp.net5中用户认证和授权(1) 基础类建立好后,下一步就要创建对基础类进行操作的类了,也就是实现基础类的增删改查(听起来不太高大上),当然,为了使用asp.net5的认证机制, ...

  5. .Net 中HashTable,HashMap 和 Dictionary<key,value> 和List<T>和DataTable的比较

    参考资料 http://www.cnblogs.com/MichaelYin/archive/2011/02/14/1954724.html http://zhidao.baidu.com/link? ...

  6. 最简实例演示asp.net5中用户认证和授权(4)

    上篇: 最简实例演示asp.net5中用户认证和授权(3) 上面我们把自定义认证和授权的相关的最小基础类和要实现的接口都实现了,下面就是如何来进行认证和授权的配置. 首先我们要告诉系统,我们的用户和角 ...

  7. 最简实例演示asp.net5中用户认证和授权(1)

    asp.net5中,关于用户的认证和授权提供了非常丰富的功能,如果结合ef7的话,可以自动生成相关的数据库表,调用也很方便. 但是,要理解这么一大堆关于认证授权的类,或者想按照自己项目的特定要求对认证 ...

  8. ASP.NET Core 6框架揭秘-实例演示版[持续更新中…]

    作为<ASP.NET Core 3框架揭秘>的升级版,<ASP.NET Core 6框架揭秘>提供了很多新的章节,同时对现有的内容进行大量的修改.虽然本书旨在对ASP.NET ...

  9. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

随机推荐

  1. JavaScript-----10.作用域

    1.作用域 一段程序代码中所用到的名字不是总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域.目的是为了提高程序的可靠性,更重要的是减少命名冲突. 在es6之前,js的作用域有:全 ...

  2. 网页前端之CSS学习记录总结篇

    标签: 块级标签block:div, p, h1-h6, ul, ol,li, dl, dt, dd,table,tr等,独占一行,可以设置宽高,默认是父标签的100%:行内标签inline:a,sp ...

  3. 【python爬虫】Xpath

    一.xml是什么 1.定义:可扩展标记性语言 2.特点:xml的是具有自描述结构的半结构化数据. 3.作用:xml主要设计宗旨是用来传输数据的.他还可以作为配置文件. 二.xml和html的区别 1. ...

  4. 基于V7的新版RL-USB和RL-FlashFS的NAND完整解决方案,实现更简单,用户仅需初始化FMC

    说明: 1.新版方案更加好用,不管用户使用的那家NAND,用户要做的仅仅是初始化FMC,其它读写API,擦写均衡,坏块管理,ECC校验和掉电保护都不用操心了. 2.新版RL-USB相比老版本功能强劲了 ...

  5. 运维工具ansible-使用与介绍(转)

    转自 Linux轻量级自动运维工具-Ansible浅析 - ~微风~ - 51CTO技术博客http://weiweidefeng.blog.51cto.com/1957995/1895261 Ans ...

  6. IT兄弟连 HTML5教程 HTML5表单 小结及习题

    小结 HTML表单提交的方法有get方法和post方法,get方法的作用是从指定的资源请求数据,post方法的作用是向指定的资源提交要被处理的数据.HTML表单一直都是Web的核心技术之一,有了它我们 ...

  7. C++ 课程设计——电梯调度系统

    这是我在本学期C++课程最后的课程设计报告,源代码将会上传到GitHub上. 一.背景 随着经济的不断发展,越来越多的摩天大楼拔地而起,而电梯作为高层建筑物种的运送人员货物的设备也越来越被广泛使用.电 ...

  8. C#_.NetFramework_Web项目_EXCEL数据导出

    [推荐阅读我的最新的Core版文章,是最全的介绍:C#_.NetCore_Web项目_EXCEL数据导出] 项目需引用NPOI的NuGet包: A-2:EXCEL数据导出--Web项目--C#代码导出 ...

  9. jvm虚拟机笔记<一> 内存区域

    运行时数据区域: 程序计数器:字节码的行号指示器. 虚拟机栈:为每个方法创建一个栈帧(存放方法中的局部变量,变量引用等). 本地方法栈:存放本地方法. ------------------------ ...

  10. easyswoole对接支付宝,微信支付

    在easyswoole中,已经开发好了相关的支付组件,只需要引入即可: composer require easyswoole/pay pay组件支持协程 支付宝 支付方法 支付宝支付目前支持 7 种 ...