1. 介绍
想象一下你有好几年的照片归档——一个文件夹里有几千个文件(Windows 用户的经典场景),现在任务是:把本年度创建的所有 .jpg 文件复制到单独的文件夹去做处理。或者你需要给所有报告文件加上前缀 "old_" 来区分旧版和新版。即便你不做照片归档,批量操作在处理数据、日志、备份或自动化时很常见,面试里也经常考到。
批量操作是练习这些东西的好机会:
- 用循环和 LINQ 遍历目录内容
- 熟练操作路径(Path)
- 过滤、搜索、按模板重命名的基础技巧
- 重要的点:安全、错误、覆盖处理
走起 — 让我们一起把文件混乱整齐起来!
2. 批量复制文件
原理:通用流程
做批量操作通常需要:
- 拿到需要的文件列表(比如某个文件夹下所有 .txt)
- 对每个文件执行需要的操作(复制、删除等)
可以用这些工具实现:
- Directory.GetFiles() — 获取文件列表,
- 使用 foreach 循环 — 逐个处理文件。
示例:把一个文件夹里的所有 .txt 文件复制到另一个文件夹
string sourceDir = @"C:\Source";
string destDir = @"C:\Target";
// 获取源文件夹中所有 .txt 文件的列表
string[] txtFiles = Directory.GetFiles(sourceDir, "*.txt");
foreach (string srcPath in txtFiles)
{
// 从完整路径获取文件名
string fileName = Path.GetFileName(srcPath);
// 构建目标文件的完整路径
string destPath = Path.Combine(destDir, fileName);
// 复制文件
File.Copy(srcPath, destPath, overwrite: true); // overwrite - 如果目标已存在,则替换
Console.WriteLine($"Copied: {fileName}");
}
Console.WriteLine("所有 .txt 文件已成功复制!");
注意:在这个例子里我们用的是过滤器 "*.txt" — 这是一个和 Windows 一样的文件名模式。
可视化(示意)
+----------------+ [*.txt] +----------------+
| C:\Source | ---filter-----> | C:\Target |
| a.txt | foreach + | |
| b.txt | --copy--------> | a.txt (copied) |
| c.jpg | | b.txt (copied) |
+----------------+ +----------------+
(.jpg 文件被忽略,只复制 .txt)
3. 批量删除文件
批量删除文件是非常常见的操作。比如清理临时文件、自动删除旧日志,或把不再需要的 jpg 照片清理掉。
示例:删除所有超过 30 天的文件
string dir = @"C:\MyLogs";
int daysOld = 30;
DirectoryInfo di = new DirectoryInfo(dir);
// 获取文件夹中的所有文件
foreach (FileInfo file in di.GetFiles())
{
// 检查最后修改时间
if (file.LastWriteTime < DateTime.Now.AddDays(-daysOld))
{
file.Delete();
Console.WriteLine($"已删除: {file.Name}");
}
}
小技巧:FileInfo.LastWriteTime 非常适合做 “比 N 天旧” 这样的判断。
4. 批量重命名文件
有时候需要按模板批量重命名文件。比如加统一前缀、改扩展名,或者顺序编号。在 .NET 里思路一样:先拿到文件列表,然后用 File.Move() 去重命名(实质是移动到相同文件夹但新名字)。
示例:给所有 .docx 文件加上前缀 "old_"
string dir = @"C:\Reports";
string[] docxFiles = Directory.GetFiles(dir, "*.docx");
foreach (string oldPath in docxFiles)
{
string dirPath = Path.GetDirectoryName(oldPath)!;
string fileName = Path.GetFileName(oldPath);
string newPath = Path.Combine(dirPath, "old_" + fileName);
// 重命名(实际上是同文件夹内移动到新名字)
File.Move(oldPath, newPath);
Console.WriteLine($"已重命名: {fileName} -> old_{fileName}");
}
重要:如果目标文件夹里已经有相同的新名字,会抛出异常。按需在循环里用 try-catch 来处理。
5. 递归复制整个目录及其内容
对单个文件夹的简单操作没问题,但 .NET 没有内建一个一行就能复制整个目录的方法(Directory.Copy 并不存在——这是个坑)。通常需要手动实现:
- 创建目标文件夹(如果不存在)
- 复制所有文件(参考上面的例子)
- 递归复制所有子文件夹(把每个子文件夹当作新的复制任务)
通用函数:递归复制文件夹
using System;
using System.IO;
class Program
{
static void CopyDirectory(string sourceDir, string destDir, bool overwrite = true)
{
// 创建目标文件夹(如果不存在)
Directory.CreateDirectory(destDir);
// 复制所有文件
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFile = Path.Combine(destDir, fileName);
File.Copy(filePath, destFile, overwrite);
}
// 递归复制所有子文件夹
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(subDir);
string destSubDir = Path.Combine(destDir, dirName);
CopyDirectory(subDir, destSubDir, overwrite);
}
}
static void Main()
{
string source = @"C:\Archive2023";
string target = @"D:\Backup2023";
CopyDirectory(source, target);
Console.WriteLine("目录已成功复制!");
}
}
流程示意
CopyDirectory(A, B)
/ \
copy files foreach subDir -> CopyDirectory(subDir, destSubDir)
6. 批量过滤、搜索和处理文件
假设你不仅要遍历一个文件夹,还要按多个条件筛选:比如只挑出大小超过 5 MB 且创建于 2024 年的图片。把 LINQ 和文件系统类组合起来很方便。
示例:列出大且新的图片的文件名
string dir = @"C:\Pictures";
var filtered = new DirectoryInfo(dir)
.GetFiles("*.jpg")
.Where(f => f.Length > 5_000_000 && f.CreationTime.Year == 2024);
foreach (var file in filtered)
{
Console.WriteLine($"{file.Name} ({file.Length / 1024 / 1024} MB)");
}
这个例子用 LINQ 串联过滤条件,写出在真实工作中常见的代码。
7. 遍历嵌套文件夹:递归与遍历
经常需要处理不仅是一个目录,而是其所有子目录(比如在整个归档结构里删除临时文件)。获取文件的方法(Directory.GetFiles 和 DirectoryInfo.GetFiles)都支持一个参数 SearchOption.AllDirectories,可以帮你把所有子目录也一起找出来!
示例:在所有子文件夹里查找并删除所有 .tmp 文件
string root = @"D:\BigFolder";
string[] tmpFiles = Directory.GetFiles(root, "*.tmp", SearchOption.AllDirectories);
foreach (string file in tmpFiles)
{
File.Delete(file);
Console.WriteLine($"已删除: {file}");
}
Console.WriteLine("所有临时文件已删除。");
注意:对这个标志要小心——它可能会找到非常深层的文件!
8. 批量创建文件和目录
有时任务正好相反——自动生成文件夹结构或批量创建很多文件。
示例:创建 10 个文件夹,每个文件夹里生成 10 个文件
string root = @"C:\GeneratedFolders";
for (int i = 1; i <= 10; i++)
{
string subDir = Path.Combine(root, $"Folder_{i}");
Directory.CreateDirectory(subDir);
for (int j = 1; j <= 10; j++)
{
string filePath = Path.Combine(subDir, $"File_{j}.txt");
File.WriteAllText(filePath, $"这是第 {j} 个文件,位于文件夹 {i}");
}
}
Console.WriteLine("文件夹和文件已创建!");
小技巧:可以快速搭建测试基础设施,生成测试用的“假数据”用于练习或教学。
9. 批量移动文件
和复制类似,不过用 File.Move 替代 File.Copy。特别适合把文件按规则分类到不同文件夹。
示例:按扩展名对文件进行分类
场景:某个文件夹里堆着各种类型文件,你想把它们按扩展名分到 .jpg、.pdf、.docx 等文件夹里。
string source = @"C:\Downloads";
string[] files = Directory.GetFiles(source);
foreach (string path in files)
{
string ext = Path.GetExtension(path).TrimStart('.').ToUpper(); // "JPG", "PDF", "DOCX"
if (string.IsNullOrEmpty(ext)) ext = "OTHER";
string destDir = Path.Combine(source, ext);
Directory.CreateDirectory(destDir); // 不用担心已经存在
string fileName = Path.GetFileName(path);
string destPath = Path.Combine(destDir, fileName);
if (!File.Exists(destPath))
{
File.Move(path, destPath);
Console.WriteLine($"已移动: {fileName} -> {destDir}");
}
else
{
Console.WriteLine($"目标文件夹已存在同名文件,跳过: {fileName}");
}
}
文件会被分类到不同“箱子”里,你会感觉自己像数码整理的 Marie Kondo(近藤麻理惠)。
10. 批量操作常见错误和注意事项
当你不是在处理单个文件而是成百上千个时,会冒出各种细节问题。
比如一些文件可能被别的程序打开——尝试删除或复制它们会报错。目标文件夹里可能已经有同名文件。如果不允许覆盖,代码会抛异常。有时文件或文件夹权限不足,或者你不小心递归删除了重要数据。
一个常见错误是完全不做错误处理:如果中间某个文件出问题(比如权限不足),整个循环就中断了,后面的文件都没处理到。所以做可靠的批量操作时,几乎总要在循环内部用 try-catch 捕获异常,遇到问题的文件先记录一下然后跳过,继续处理其他文件。
别忘了考虑覆盖策略:在合适的场景下,可以在 File.Copy 里用 overwrite: true,重命名或移动前先检查目标是否存在,或者采用冲突处理策略(加后缀重命名、跳过、记录日志等)。
GO TO FULL VERSION