C#实现按文件名规则将多层目录文件移动到对应指定文件夹
功能实现说明
现有代码仅完成了目标文件夹创建逻辑,未实现实际文件移动,且原有逻辑仅遍历根目录文件、无异常容错,无法适配数千万级多子文件夹文件的批量移动需求。
原有代码片段:
using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; namespace File2Folders { public partial class Form1 : Form { public Form1() { InitializeComponent(); } List<string> fileName = null; List<string> fileNames = null; private void btn_browse_Click(object sender, EventArgs e) { using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { if (fbd.ShowDialog() == DialogResult.OK) { listBox1.Items.Clear(); // 原有逻辑仅读取根目录文件,未遍历子文件夹 fileName = Directory.GetFiles(fbd.SelectedPath).ToList(); fileNames = fileName.Select(item => Path.GetFileNameWithoutExtension(item).Substring(1, 4)).OrderBy(n=>n).ToList(); listBox1.DataSource = fileName.Select(f => Path.GetFileName(f)).ToList(); listBox2.DataSource = fileNames; } } } private void btn_move_Click(object sender, EventArgs e) { using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { if (fbd.ShowDialog() == DialogResult.OK) { // 原有逻辑仅创建文件夹,未执行移动 fileNames.ForEach(item => Directory.CreateDirectory(Path.Combine(fbd.SelectedPath, item))); }
改造后完整实现
核心改造点:
- 用
Directory.EnumerateFiles递归遍历所有子文件夹文件,采用迭代读取方式,避免一次性加载数千万文件路径导致内存溢出 - 增加文件名长度校验,避免短文件名调用
Substring时抛出索引越界异常 - 增加重名文件处理逻辑,默认给重名文件追加序号后缀,不会中断批量任务
- 增加单文件异常捕获,单个文件移动失败不影响整体任务执行
- 移动操作放在后台线程执行,避免UI界面卡死
完整代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; namespace File2Folders { public partial class Form1 : Form { private string _sourcePath = string.Empty; public Form1() { InitializeComponent(); } private void btn_browse_Click(object sender, EventArgs e) { using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { if (fbd.ShowDialog() == DialogResult.OK) { _sourcePath = fbd.SelectedPath; listBox1.Items.Clear(); listBox2.Items.Clear(); // 仅预览前100个文件,避免全量加载导致卡顿 var previewFiles = Directory.EnumerateFiles(_sourcePath, "*", SearchOption.AllDirectories) .Take(100) .Select(f => Path.GetFileName(f)) .ToList(); listBox1.Items.AddRange(previewFiles.ToArray()); MessageBox.Show("已选择源目录,因文件量较大仅预览前100个文件"); } } } private async void btn_move_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(_sourcePath)) { MessageBox.Show("请先选择要处理的源文件目录"); return; } using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { if (fbd.ShowDialog() == DialogResult.OK) { string targetRootPath = fbd.SelectedPath; btn_move.Enabled = false; long movedCount = 0; long errorCount = 0; try { // 后台执行移动操作,不阻塞UI线程 await Task.Run(() => { // 递归遍历所有子文件夹文件,逐次返回文件路径,内存占用极低 foreach (string filePath in Directory.EnumerateFiles(_sourcePath, "*", SearchOption.AllDirectories)) { try { string fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath); // 校验文件名长度,避免短文件名报错 if (fileNameWithoutExt.Length < 5) { errorCount++; continue; } // 匹配原有规则:取文件名第2位开始的4个字符作为目标文件夹名 string targetFolderName = fileNameWithoutExt.Substring(1, 4); string targetFolderPath = Path.Combine(targetRootPath, targetFolderName); // 文件夹不存在才创建,减少重复IO操作 if (!Directory.Exists(targetFolderPath)) { Directory.CreateDirectory(targetFolderPath); } string targetFilePath = Path.Combine(targetFolderPath, Path.GetFileName(filePath)); // 重名文件自动追加序号 int dupIndex = 1; while (File.Exists(targetFilePath)) { string dupFileName = $"{Path.GetFileNameWithoutExtension(filePath)}_{dupIndex}{Path.GetExtension(filePath)}"; targetFilePath = Path.Combine(targetFolderPath, dupFileName); dupIndex++; } // 执行文件移动 File.Move(filePath, targetFilePath); movedCount++; // 每处理1000个文件更新一次进度 if (movedCount % 1000 == 0) { Invoke(new Action(() => { listBox2.Items.Add($"已移动{movedCount}个文件"); listBox2.TopIndex = listBox2.Items.Count - 1; })); } } catch { errorCount++; } } }); MessageBox.Show($"移动完成,成功移动{movedCount}个文件,失败{errorCount}个文件"); } catch (Exception ex) { MessageBox.Show($"执行出错:{ex.Message}"); } finally { btn_move.Enabled = true; } } } } } }
注意:正式处理数千万文件前,建议先拿少量测试文件验证文件夹命名规则、移动逻辑符合预期,避免规则错误导致文件归类混乱。
内容的提问来源于stack exchange,提问作者Mohini Singh
相关产品推荐
相关产品推荐

