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

C#如何优化DataTable与FileInfo的大型嵌套循环运行效率

性能问题核心原因

你当前代码的时间复杂度达到了O(数据表行数 × 文件总数 × 分类规则数),15万行数据和20万文件直接嵌套循环,会产生300亿次比对操作,再加上循环内反复扫盘、重复计算、手动触发GC等错误写法,耗时高是必然结果。

具体优化方案
  • 把所有一次性磁盘操作移到最外层,只做一次
    你现在每遍历1行数据表,就重新调用GetFiles扫描一次20万文件的源目录,还重复创建FileInfo对象取文件大小,等于重复扫盘15万次,这部分开销占总耗时的90%以上。
    正确做法是在进入所有数据行循环之前,只扫描一次源目录,把所有文件信息预处理后存入内存字典:

    1. 提前编译你用来处理文件名括号后缀的正则,避免循环内反复解析正则表达式
    2. 遍历所有文件时,就提前把文件名的 (数字).后缀替换好、把文件大小换算成kB单位
    3. 用(处理后的无后缀文件名, 文件大小kB)作为键,对应原始文件信息作为值存入Dictionary,后续查表的时间复杂度是O(1),完全不需要再遍历全量文件。
      参考预处理逻辑:
    // 以下逻辑只执行1次,放在所有行循环外面
    DirectoryInfo sourceDir = new DirectoryInfo(Settings.Default.PathtoGesamtablage);
    FileInfo[] allSourceFiles = sourceDir.GetFiles("*.*", SearchOption.TopDirectoryOnly);
    // 提前编译正则
    Regex suffixRegex = new Regex(@"\s\([0-9]+\)\.", RegexOptions.Compiled);
    // 构建文件索引
    Dictionary<(string fileNameNoExt, long sizeKb), List<FileInfo>> fileIndex = new();
    foreach (FileInfo f in allSourceFiles)
    {
        string processedName = suffixRegex.Replace(f.Name, ".");
        string nameNoExt = Path.GetFileNameWithoutExtension(processedName);
        long sizeKb = (long)Math.Ceiling(f.Length / 1024.0);
        var key = (nameNoExt, sizeKb);
        if (!fileIndex.ContainsKey(key))
            fileIndex[key] = new List<FileInfo>();
        fileIndex[key].Add(f);
    }
    

    仅这一步优化,就能把原来17天的预期耗时压缩到几小时级别。

  • 预处理分类规则,减少重复判断
    你现在每匹配到一个文件,就遍历newtable全量规则做StartsWith/Contains判断,属于重复开销。可以在循环外提前把规则拆成「前缀匹配规则列表」和「包含匹配规则列表」两个集合,提前把规则里的文件名、目标文件夹名取出来存成字符串,不用每次从DataRow里取值转换。
    字符串匹配时记得指定StringComparison.Ordinal(如果需要大小写不敏感就用OrdinalIgnoreCase),比默认的字符串比较速度快3-5倍。

  • 删掉所有拖慢性能的无效操作

    • 立刻删掉循环内的GC.Collect():手动强制全量垃圾回收会挂起所有线程,你每处理一行就调用一次,会平白增加几十上百倍的GC开销,运行时会自动管理GC,完全不需要手动触发。
    • 用HashSet<string>记录已经创建过的目录路径,不要每次都调用Directory.Exists做磁盘检查,创建过的目录直接跳过。
    • 同样用HashSet记录已经复制完成的目标文件路径,减少不必要的File.Exists磁盘调用。
    • 所有路径拼接统一用Path.Combine(),不要手动拼接\\,既避免路径格式错误,性能也更好。
    • 你代码里folderfound为空时OrdnermitUnterodner根本没有赋值,对应的File.Exists判断属于无效逻辑,直接删掉即可。
  • 处理完成的文件及时从索引中移除
    每次匹配到文件、完成复制删除操作后,直接把这个文件从内存的文件索引字典里移除,后续行匹配时不会再遍历到已经处理过的文件,越往后处理速度越快。

  • 可选:可控的并行处理进一步提速
    文件复制属于IO密集型操作,可以用Parallel.ForEach分块处理数据表行,根据磁盘性能设置最大并行度:机械盘设为2,SSD可以设为4-8,文件操作部分加轻量锁避免多线程写同一路径冲突,这一步还能把总耗时再压缩50%以上。

按以上方案优化后,总操作量会从原来的300亿次降到百万级别,全量任务处理完的耗时通常在10分钟到1小时之间,具体取决于磁盘读写性能。

内容的提问来源于stack exchange,提问作者Raphael Rieber

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:03:18