C#如何优化DataTable与FileInfo的大型嵌套循环运行效率
你当前代码的时间复杂度达到了O(数据表行数 × 文件总数 × 分类规则数),15万行数据和20万文件直接嵌套循环,会产生300亿次比对操作,再加上循环内反复扫盘、重复计算、手动触发GC等错误写法,耗时高是必然结果。
把所有一次性磁盘操作移到最外层,只做一次
你现在每遍历1行数据表,就重新调用GetFiles扫描一次20万文件的源目录,还重复创建FileInfo对象取文件大小,等于重复扫盘15万次,这部分开销占总耗时的90%以上。
正确做法是在进入所有数据行循环之前,只扫描一次源目录,把所有文件信息预处理后存入内存字典:- 提前编译你用来处理文件名括号后缀的正则,避免循环内反复解析正则表达式
- 遍历所有文件时,就提前把文件名的
(数字).后缀替换好、把文件大小换算成kB单位 - 用
(处理后的无后缀文件名, 文件大小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

