You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:38:12