如何优化C#中foreach循环内Switch语句的执行速度,提升多目录大文件数据处理性能
针对大规模文件行处理的性能优化方案
哇,你这个性能问题的核心坑太明显了——每处理一行就重新读取整个文件到DataTable,这对于10万行甚至380万行的文件来说,IO和内存消耗直接爆炸,不慢才怪😂 下面给你一步步拆解优化方案,从最致命的问题开始解决:
1. 先把重复读文件这个致命问题干掉
原代码里MethodToCreateDatatale(FilePath)被放在了行循环内部,意味着每处理一行就要重新加载整个文件,这是性能灾难的根源。必须改成每个文件只读取一次,要么缓存整个文件的数据,要么读取时只提取需要处理的行。
方案A:缓存DataTable(适合中等大小文件)
// 设置并行选项,避免磁盘IO过载 var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount // 可根据磁盘性能调整,比如4-8 }; Parallel.ForEach(BreakingTheMasteList, parallelOptions, items => { // 用Path.Combine拼接路径,避免手动拼字符串出错 string FilePath = Path.Combine(someDir, items.Key.Item1.ToString(), items.Key.Item2.ToString()); // 一次性读取整个文件到DataTable,只做一次! DataTable dt = MethodToCreateDatatale(FilePath); // 获取当前文件所有需要处理的行范围 var lineRanges = items.Select(y => Tuple.Create(y.Item3, y.Item4)).ToList(); foreach(var range in lineRanges) { int startingLine = range.Item1; int endLine = range.Item2; // 先做边界检查,避免越界报错 if (startingLine < 0 || endLine > dt.Rows.Count) { // 记录无效范围日志,直接跳过 continue; } // 遍历需要处理的行 for(int i = startingLine; i < endLine; i++) { string X = dt.Rows[i].Field<string>(0); string Y = dt.Rows[i].Field<string>(1); // 后续Enum解析和业务逻辑... } } });
方案B:逐行读取筛选(适合380万行的超大型文件)
如果文件太大,DataTable会占用过多内存,直接用StreamReader逐行读取,只处理需要的行:
Parallel.ForEach(BreakingTheMasteList, parallelOptions, items => { string FilePath = Path.Combine(someDir, items.Key.Item1.ToString(), items.Key.Item2.ToString()); var lineRanges = items.Select(y => Tuple.Create(y.Item3, y.Item4)).ToList(); // 把需要处理的行号存入HashSet,快速判断是否需要处理当前行 var targetLines = new HashSet<int>(); foreach(var range in lineRanges) { for(int i = range.Item1; i < range.Item2; i++) { targetLines.Add(i); } } // 逐行读取文件,跳过不需要处理的行 using(var reader = new StreamReader(FilePath)) { int currentLine = 0; string line; while((line = reader.ReadLine()) != null) { if(targetLines.Contains(currentLine)) { // 根据你的文件格式分割行数据,这里假设是CSV var parts = line.Split(','); string X = parts[0]; string Y = parts[1]; // 后续Enum解析和业务逻辑... } currentLine++; } } });
2. 优化Enum解析的重复操作
原代码里Enum解析的写法还有错误(Enum.TryParse(X, EN)应该是Enum.TryParse(X, out EN)),而且每次都重复调用解析方法,性能浪费严重。可以提前把枚举值缓存到字典里:
// 提前初始化枚举映射字典,程序启动时只做一次 private static readonly Dictionary<string, aPredefinedEnum> _enumLookup = Enum.GetValues(typeof(aPredefinedEnum)) .Cast<aPredefinedEnum>() .ToDictionary(e => e.ToString(), e => e, StringComparer.OrdinalIgnoreCase); // 按需选择是否忽略大小写 // 在处理行时: aPredefinedEnum EN; // 直接从字典取,比每次TryParse快很多 if(!_enumLookup.TryGetValue(X, out EN)) { EN = aPredefinedEnum.DfVal; }
3. 用委托字典替代380个case的Switch
380个case的Switch不仅代码臃肿,性能也不如字典映射委托。把每个枚举对应的业务逻辑封装成委托,提前存入字典:
// 提前初始化业务逻辑委托字典 private static readonly Dictionary<aPredefinedEnum, Action<string, int, int>> _enumActions = new Dictionary<aPredefinedEnum, Action<string, int, int>> { { aPredefinedEnum.Q0, (y, startLine, endLine) => { // 把Enumerable.Range改成直接数值比较,快很多! short val = short.Parse(y); if(val < 0 || val >= 2) { // 错误日志记录、捕获行范围等逻辑 } } }, { aPredefinedEnum.Q1, (y, startLine, endLine) => { // Q1对应的业务逻辑 } }, // ... 依次添加其他378个枚举的逻辑 }; // 在处理行时: if(_enumActions.TryGetValue(EN, out var action)) { action(Y, startingLine, endLine); }
这种方式不仅代码更整洁,执行效率也比Switch高不少,尤其是case数量极多的时候。
4. 其他小细节优化
- 把
Enumerable.Range(0, 2).Contains(Int16.Parse(Y))改成直接数值比较:short val = short.Parse(Y); if(val <0 || val >=2),避免创建临时Range集合,性能提升明显。 - 合并重叠的行范围:比如如果有(1,39)和(35,50),可以合并成(1,50),减少重复遍历行的次数。
- 确保日志组件是线程安全的:如果在并行任务中记录日志,不要用非线程安全的日志对象,必要时加锁或者用线程安全的日志库。
内容的提问来源于stack exchange,提问作者n00b
相关产品推荐
相关产品推荐

