C#获取本地磁盘目录与文件处理实战源码
简介:在C#开发中,获取本地磁盘目录并处理文件是常见的基础操作,涉及 System.IO 命名空间中的 DirectoryInfo 、文件遍历、递归处理、权限控制等内容。本实例源码详细演示了如何使用C#实现磁盘目录的获取与遍历,包含同步与异步方法、LINQ查询、异常处理、路径操作等关键技术点。通过完整示例代码,开发者可以快速掌握文件系统的操作技巧,并应用于实际项目中。
1. C#文件系统操作概述
在现代软件开发中,文件系统操作是构建稳定、高效应用程序的基础环节之一。C#作为微软生态中核心的编程语言之一,提供了强大的System.IO命名空间来支持开发者对文件和目录的管理。本章将引导读者了解C#在Windows平台下进行文件系统操作的基本机制。
通过System.IO中的关键类如 Directory 、 File 、 Path 、 DirectoryInfo 和 FileInfo ,我们将掌握如何创建、读取、更新和删除文件与目录资源。同时,本章也将简要介绍文件与目录之间的逻辑关系,并讨论如何在代码中安全地访问本地磁盘,避免权限异常和资源冲突。这将为后续章节中更复杂的数据遍历、筛选与处理打下坚实的基础。
2. DirectoryInfo类的使用
在C#中, DirectoryInfo 类是 System.IO 命名空间下的核心类之一,用于封装对文件系统中目录的操作。与静态类 Directory 相比, DirectoryInfo 提供了面向对象的编程方式,更适合处理需要多次操作目录对象的场景。本章将详细介绍 DirectoryInfo 类的使用方法,包括其基本功能、与其他IO类的对比、常见操作方法以及在多线程环境下的性能优化策略。
2.1 DirectoryInfo类的基本功能
DirectoryInfo 类不仅提供了获取目录基本信息的能力,还支持创建、删除、移动等常用操作。通过实例化一个 DirectoryInfo 对象,可以更高效地进行目录操作,尤其是在需要多次访问同一目录时。
2.1.1 DirectoryInfo类的定义与实例化
DirectoryInfo 类位于 System.IO 命名空间中,其构造函数接收一个表示目录路径的字符串。通过构造函数可以创建一个表示特定目录的对象。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\ExampleDirectory";
DirectoryInfo directoryInfo = new DirectoryInfo(path);
Console.WriteLine($"Directory Path: {directoryInfo.FullName}");
}
}
代码逻辑分析:
- 第1行引入
System命名空间,用于控制台输出。 - 第2行引入
System.IO命名空间,以便使用DirectoryInfo类。 - 第6行定义变量
path,表示目标目录路径。 - 第7行使用
new DirectoryInfo(path)实例化一个DirectoryInfo对象,指向指定路径。 - 第9行输出该目录的完整路径(
FullName属性)。
参数说明:
path:表示目录的路径字符串,可以是绝对路径或相对路径。directoryInfo:实例化后的对象,封装了对目标目录的所有操作能力。
2.1.2 获取目录的基本信息(如创建时间、名称、父目录等)
DirectoryInfo 类提供了多个属性用于获取目录的基本信息,包括创建时间、父目录、是否为只读等。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\ExampleDirectory";
DirectoryInfo directoryInfo = new DirectoryInfo(path);
Console.WriteLine($"Name: {directoryInfo.Name}");
Console.WriteLine($"Parent: {directoryInfo.Parent?.Name ?? "No parent"}");
Console.WriteLine($"Creation Time: {directoryInfo.CreationTime}");
Console.WriteLine($"Attributes: {directoryInfo.Attributes}");
}
}
代码逻辑分析:
- 第8行输出目录名称(
Name属性)。 - 第9行输出父目录名称,使用
?.运算符避免空引用异常。 - 第10行输出目录创建时间。
- 第11行输出目录的属性(如只读、隐藏等)。
参数说明:
| 属性名 | 类型 | 说明 |
|---|---|---|
| Name | string | 当前目录的名称 |
| Parent | DirectoryInfo | 父目录对象,可能为null |
| CreationTime | DateTime | 目录创建时间 |
| Attributes | FileAttributes | 目录的属性标志,如只读、隐藏 |
mermaid流程图:
graph TD
A[创建DirectoryInfo实例] --> B{路径是否存在}
B -->|是| C[获取目录信息]
B -->|否| D[抛出异常]
C --> E[输出Name]
C --> F[输出Parent]
C --> G[输出CreationTime]
C --> H[输出Attributes]
2.2 DirectoryInfo与其他IO类的比较
在C#中,除了 DirectoryInfo ,还有 Directory 、 FileInfo 和 File 等类用于文件和目录操作。它们各有优劣,适用于不同场景。
2.2.1 DirectoryInfo与Directory类的差异
| 特性 | DirectoryInfo | Directory(静态类) |
|---|---|---|
| 实例化方式 | 需要实例化 | 直接调用静态方法 |
| 性能 | 多次操作时更高效 | 每次调用都重新访问文件系统 |
| 面向对象特性 | 支持面向对象操作 | 不支持面向对象 |
| 属性访问 | 可缓存属性 | 每次调用都重新读取属性 |
| 适用场景 | 需要多次操作同一目录时 | 单次操作目录 |
示例对比:
// 使用 DirectoryInfo
DirectoryInfo dir = new DirectoryInfo(@"C:\ExampleDirectory");
Console.WriteLine(dir.Name);
Console.WriteLine(dir.Exists);
// 使用 Directory
Console.WriteLine(Directory.Exists(@"C:\ExampleDirectory"));
Console.WriteLine(Directory.GetParent(@"C:\ExampleDirectory")?.Name);
结论:
DirectoryInfo更适合需要多次访问目录对象的场景。Directory类适合一次性操作目录,使用更简洁。
2.2.2 FileInfo与File类的使用场景对比
| 特性 | FileInfo | File(静态类) |
|---|---|---|
| 实例化方式 | 需要实例化 | 直接调用静态方法 |
| 性能 | 多次操作时更高效 | 每次调用都重新访问文件系统 |
| 属性访问 | 可缓存属性 | 每次调用都重新读取属性 |
| 适用场景 | 需要多次操作同一文件时 | 单次操作文件 |
示例对比:
// 使用 FileInfo
FileInfo file = new FileInfo(@"C:\ExampleDirectory\test.txt");
Console.WriteLine(file.Length);
Console.WriteLine(file.Exists);
// 使用 File
Console.WriteLine(File.Exists(@"C:\ExampleDirectory\test.txt"));
Console.WriteLine(new FileInfo(@"C:\ExampleDirectory\test.txt").Length);
2.3 DirectoryInfo的常见操作方法
DirectoryInfo 类提供了多种操作目录的方法,包括创建、删除、移动等,适用于各种文件系统操作需求。
2.3.1 创建与删除目录
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\NewDirectory";
DirectoryInfo directoryInfo = new DirectoryInfo(path);
if (!directoryInfo.Exists)
{
directoryInfo.Create();
Console.WriteLine("Directory created.");
}
// 删除目录
if (directoryInfo.Exists)
{
directoryInfo.Delete(true); // true表示递归删除子目录和文件
Console.WriteLine("Directory deleted.");
}
}
}
代码逻辑分析:
- 第8行检查目录是否存在。
- 第10行调用
Create()方法创建目录。 - 第15行调用
Delete(true)递归删除目录及其内容。
2.3.2 移动目录与重命名
虽然 DirectoryInfo 类本身不直接支持重命名操作,但可以通过 MoveTo 方法实现。
using System;
using System.IO;
class Program
{
static void Main()
{
string sourcePath = @"C:\OldDirectoryName";
string destPath = @"C:\NewDirectoryName";
DirectoryInfo directoryInfo = new DirectoryInfo(sourcePath);
if (directoryInfo.Exists)
{
directoryInfo.MoveTo(destPath);
Console.WriteLine("Directory renamed/moved.");
}
}
}
参数说明:
sourcePath:原始目录路径。destPath:目标路径,可以是新的目录名或新位置。
2.3.3 获取目录的属性信息
DirectoryInfo 类提供了一系列属性用于获取目录的详细信息:
| 属性名 | 类型 | 说明 |
|---|---|---|
| Exists | bool | 目录是否存在 |
| FullName | string | 目录的完整路径 |
| Extension | string | 目录扩展名(通常为空) |
| CreationTime | DateTime | 创建时间 |
| LastAccessTime | DateTime | 最后一次访问时间 |
| LastWriteTime | DateTime | 最后一次修改时间 |
| Attributes | FileAttributes | 目录属性(如隐藏、只读等) |
2.4 DirectoryInfo的线程安全性与性能优化
在多线程环境中操作文件系统时,需要注意线程安全性和性能优化问题。
2.4.1 多线程环境下的目录访问
DirectoryInfo 类本身不是线程安全的,多个线程同时访问同一 DirectoryInfo 实例可能会导致数据不一致或异常。
解决方案:
- 使用锁机制(
lock)确保同一时间只有一个线程访问。 - 避免共享
DirectoryInfo实例,每个线程独立操作。
private static readonly object lockObj = new object();
public static void SafeDirectoryAccess(string path)
{
lock (lockObj)
{
DirectoryInfo dir = new DirectoryInfo(path);
if (dir.Exists)
{
Console.WriteLine(dir.Name);
}
}
}
2.4.2 使用缓存提升访问效率
频繁访问同一目录时,可以将 DirectoryInfo 对象缓存起来,避免重复实例化和系统调用。
private static Dictionary<string, DirectoryInfo> directoryCache = new Dictionary<string, DirectoryInfo>();
public static DirectoryInfo GetCachedDirectory(string path)
{
if (!directoryCache.TryGetValue(path, out DirectoryInfo dir))
{
dir = new DirectoryInfo(path);
directoryCache[path] = dir;
}
return dir;
}
优点:
- 减少系统调用次数,提高性能。
- 避免重复创建对象,节省内存资源。
注意事项:
- 缓存对象需要考虑生命周期管理,避免内存泄漏。
- 文件系统状态变化后,缓存数据可能失效,需及时更新或清除。
3. GetDirectories方法获取子目录与GetFiles方法读取目录文件
3.1 获取子目录与文件的核心方法
3.1.1 GetDirectories方法的使用方式
GetDirectories 方法是 DirectoryInfo 类的重要成员之一,用于获取指定目录下的所有子目录。该方法有多个重载版本,最基础的使用方式如下:
DirectoryInfo rootDir = new DirectoryInfo(@"C:\Example");
DirectoryInfo[] subDirectories = rootDir.GetDirectories();
逐行代码解析:
- 第1行:实例化一个
DirectoryInfo对象,传入目标目录路径。 - 第2行:调用
GetDirectories()方法,返回当前目录下的所有子目录数组。
方法签名:
public DirectoryInfo[] GetDirectories();
该方法返回的是 DirectoryInfo[] 类型数组,其中每个元素都代表一个子目录。如果目录中没有子目录,则返回一个长度为0的数组。
3.1.2 GetFiles方法的参数详解
GetFiles 方法用于获取指定目录下的所有文件。与 GetDirectories 类似,它也提供了多个重载方法,以支持不同的搜索条件。
基础用法:
DirectoryInfo dir = new DirectoryInfo(@"C:\Example");
FileInfo[] files = dir.GetFiles();
此代码获取目录下所有文件,不进行任何筛选。
带搜索模式的用法:
FileInfo[] csFiles = dir.GetFiles("*.cs");
这里传入了 "*.cs" 作为搜索模式,表示只获取扩展名为 .cs 的文件。
完整参数用法:
FileInfo[] files = dir.GetFiles("*.txt", SearchOption.AllDirectories);
- 第一个参数是搜索模式,例如
"*.txt"。 - 第二个参数是
SearchOption枚举,用于控制搜索范围: SearchOption.TopDirectoryOnly:仅当前目录。SearchOption.AllDirectories:递归搜索所有子目录。
表格:GetDirectories 与 GetFiles 方法对比
| 方法名 | 返回类型 | 是否支持搜索模式 | 是否支持递归搜索 | 常见用途 |
|---|---|---|---|---|
| GetDirectories() | DirectoryInfo[] | 否 | 否 | 获取子目录 |
| GetFiles() | FileInfo[] | 是 | 是 | 获取文件,支持筛选与递归搜索 |
3.2 筛选目录与文件的高级技巧
3.2.1 使用搜索模式匹配文件名
GetFiles 支持使用通配符进行文件名匹配。常见的通配符包括:
*:匹配任意数量的字符?:匹配单个字符
示例:
// 获取所有 .cs 和 .vb 文件
FileInfo[] codeFiles = dir.GetFiles("*.cs");
FileInfo[] vbFiles = dir.GetFiles("*.vb");
进阶技巧:结合通配符灵活筛选
// 匹配以 "log" 开头的 txt 文件
FileInfo[] logFiles = dir.GetFiles("log*.txt");
这样可以获取类似 log1.txt 、 logfile.txt 等文件。
3.2.2 搜索选项(TopDirectoryOnly 与 AllDirectories)
SearchOption 枚举是 GetDirectories 和 GetFiles 的可选参数,用于控制搜索的范围。
示例:
// 仅当前目录下的所有 .txt 文件
FileInfo[] topFiles = dir.GetFiles("*.txt", SearchOption.TopDirectoryOnly);
// 当前目录及其所有子目录中的 .txt 文件
FileInfo[] allFiles = dir.GetFiles("*.txt", SearchOption.AllDirectories);
流程图:展示搜索范围差异
graph TD
A[目录结构] --> B{搜索选项}
B -->|TopDirectoryOnly| C[仅当前目录]
B -->|AllDirectories| D[递归遍历所有子目录]
性能提示:
- 使用
AllDirectories会显著增加 I/O 操作次数,尤其在处理大型目录结构时,应谨慎使用。 - 若仅需当前目录内容,建议始终使用
TopDirectoryOnly以提升性能。
3.3 获取结果的处理与优化
3.3.1 遍历大型目录结构的性能考量
当处理包含大量子目录和文件的路径时, GetDirectories 和 GetFiles 的性能会受到显著影响。主要瓶颈包括:
- 磁盘访问延迟 :频繁读取文件系统元数据。
- 内存占用 :返回的数组可能包含大量对象,占用较多内存。
- UI冻结 :在图形界面中直接调用可能导致界面卡顿。
优化策略:
- 分页加载 :按需加载目录内容,而非一次性获取全部。
- 缓存目录信息 :将已获取的目录/文件信息缓存,避免重复读取。
- 异步加载 :通过
Task或BackgroundWorker在后台加载数据。
3.3.2 并行处理目录与文件的策略
为了提升性能,可以使用多线程或并行 LINQ(PLINQ)来处理目录内容。
示例:并行处理文件列表
var files = dir.GetFiles("*.log", SearchOption.AllDirectories);
Parallel.ForEach(files, file =>
{
Console.WriteLine($"处理文件: {file.FullName}");
// 模拟耗时操作
Thread.Sleep(10);
});
逐行解释:
Parallel.ForEach:使用并行方式遍历每个文件。file.FullName:获取文件的完整路径。Thread.Sleep(10):模拟处理文件的耗时操作。
注意事项:
- 并行处理应控制线程数量,避免资源争用。
- 若处理逻辑涉及共享资源(如日志文件),需使用锁机制确保线程安全。
对比表格:串行与并行处理性能对比
| 处理方式 | 时间复杂度 | 是否适合大数据 | 是否易导致资源竞争 | 适用场景 |
|---|---|---|---|---|
| 串行 | O(n) | 否 | 否 | 小型目录或单线程环境 |
| 并行 | O(n/p) | 是 | 是 | 大型目录、后台处理任务 |
3.4 获取目录信息的异常处理机制
3.4.1 访问受限目录时的异常捕获
在实际应用中,某些目录可能因权限问题无法访问,此时调用 GetDirectories() 或 GetFiles() 会抛出 UnauthorizedAccessException 异常。
示例代码:捕获权限异常
try
{
DirectoryInfo dir = new DirectoryInfo(@"C:\Windows\System32");
DirectoryInfo[] subDirs = dir.GetDirectories();
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"无法访问目录:{ex.Message}");
}
异常处理建议:
- 使用
try-catch块包裹文件系统访问代码。 - 提供用户提示或日志记录,便于排查问题。
- 对于系统关键目录(如
C:\Windows),应避免直接访问。
3.4.2 文件正在使用或被锁定的处理
当某个文件正在被其他进程使用时,尝试访问它可能会引发 IOException 。
示例:处理被锁定的文件
try
{
FileInfo[] files = dir.GetFiles("*.log");
foreach (var file in files)
{
using (FileStream fs = file.OpenRead())
{
// 读取文件内容
}
}
}
catch (IOException ex)
{
Console.WriteLine($"文件正在使用中:{ex.Message}");
}
处理策略:
- 在打开文件前检查是否被锁定。
- 使用
FileShare参数控制文件访问权限。 - 若文件无法立即访问,可延迟重试或跳过处理。
异常类型与对应处理建议表:
| 异常类型 | 原因说明 | 建议处理方式 |
|---|---|---|
| UnauthorizedAccessException | 没有访问权限 | 检查运行权限或跳过该目录 |
| IOException | 文件正在使用或磁盘错误 | 重试或跳过当前文件 |
| DirectoryNotFoundException | 指定路径不存在 | 检查路径有效性或跳过 |
| SecurityException | 安全策略限制访问 | 调整应用程序权限或联系管理员 |
本章总结:
通过对 DirectoryInfo 的 GetDirectories 和 GetFiles 方法的深入解析,我们掌握了如何高效获取目录结构与文件列表的方法,并结合搜索模式、递归选项和异常处理机制,提升了代码的健壮性与性能。下一章将探讨如何通过递归方式遍历目录结构,并结合 LINQ 实现更灵活的文件集合处理。
4. 递归遍历目录结构与使用LINQ处理文件集合
4.1 递归遍历的实现原理
4.1.1 基于递归函数的目录遍历
递归是一种在编程中常用的解决问题的方法,特别适用于需要遍历树状结构的场景,例如文件系统中的目录结构。递归函数通过调用自身来处理子问题,直到达到终止条件。
在C#中,使用 DirectoryInfo 类可以实现目录的递归遍历。以下是一个基本的递归函数示例:
public void RecursiveDirectorySearch(DirectoryInfo root)
{
try
{
// 获取当前目录下的所有子目录
foreach (DirectoryInfo dir in root.GetDirectories())
{
Console.WriteLine($"Directory: {dir.FullName}");
RecursiveDirectorySearch(dir); // 递归调用
}
// 获取当前目录下的所有文件
foreach (FileInfo file in root.GetFiles())
{
Console.WriteLine($"File: {file.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing {root.FullName}: {ex.Message}");
}
}
代码逻辑分析:
- 入口参数 :
DirectoryInfo root表示要开始遍历的根目录。 - 递归结构 :
-foreach (DirectoryInfo dir in root.GetDirectories()):获取当前目录下的所有子目录。
-RecursiveDirectorySearch(dir):对每个子目录进行递归调用,实现深度优先遍历。 - 文件处理 :
-foreach (FileInfo file in root.GetFiles()):获取当前目录下的所有文件并输出路径。 - 异常处理 :使用
try-catch块捕获访问受限目录时可能出现的异常。
参数说明:
DirectoryInfo:用于表示文件系统中的目录,提供获取子目录、文件和属性的方法。GetDirectories():返回当前目录下的所有子目录。GetFiles():返回当前目录下的所有文件。
递归流程图:
graph TD
A[开始遍历] --> B{是否有子目录?}
B -->|是| C[遍历子目录]
C --> D[递归调用自身]
D --> E[继续遍历下一级目录]
B -->|否| F[遍历当前目录下的文件]
F --> G[输出文件信息]
E --> H[返回上一级]
4.1.2 使用队列与栈实现非递归遍历
虽然递归方法简洁易懂,但在处理非常大的目录结构时可能会导致栈溢出。为了避免这个问题,可以使用队列(广度优先)或栈(深度优先)实现非递归的目录遍历。
使用队列实现广度优先遍历:
public void BreadthFirstDirectorySearch(string rootPath)
{
Queue<DirectoryInfo> directoryQueue = new Queue<DirectoryInfo>();
DirectoryInfo root = new DirectoryInfo(rootPath);
directoryQueue.Enqueue(root);
while (directoryQueue.Count > 0)
{
DirectoryInfo currentDir = directoryQueue.Dequeue();
Console.WriteLine($"Directory: {currentDir.FullName}");
try
{
foreach (DirectoryInfo dir in currentDir.GetDirectories())
{
directoryQueue.Enqueue(dir); // 将子目录加入队列
}
foreach (FileInfo file in currentDir.GetFiles())
{
Console.WriteLine($"File: {file.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing {currentDir.FullName}: {ex.Message}");
}
}
}
使用栈实现深度优先遍历:
public void DepthFirstDirectorySearch(string rootPath)
{
Stack<DirectoryInfo> directoryStack = new Stack<DirectoryInfo>();
DirectoryInfo root = new DirectoryInfo(rootPath);
directoryStack.Push(root);
while (directoryStack.Count > 0)
{
DirectoryInfo currentDir = directoryStack.Pop();
Console.WriteLine($"Directory: {currentDir.FullName}");
try
{
// 深度优先,先处理子目录
foreach (DirectoryInfo dir in currentDir.GetDirectories())
{
directoryStack.Push(dir); // 将子目录压入栈
}
foreach (FileInfo file in currentDir.GetFiles())
{
Console.WriteLine($"File: {file.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing {currentDir.FullName}: {ex.Message}");
}
}
}
对比分析:
| 方法类型 | 遍历顺序 | 数据结构 | 适用场景 |
|---|---|---|---|
| 递归 | 深度优先 | 调用栈 | 简单目录结构 |
| 队列(广度优先) | 广度优先 | 队列 | 大型目录结构,需按层处理 |
| 栈(深度优先) | 深度优先 | 栈 | 大型目录结构,需深度优先 |
4.2 递归遍历的优化与控制
4.2.1 限制递归深度与遍历范围
在实际应用中,可能并不需要遍历整个目录树,而是仅关注特定深度的子目录。可以通过传递当前深度参数,并设置最大深度来控制递归的终止。
public void LimitedRecursiveSearch(DirectoryInfo root, int currentDepth, int maxDepth)
{
if (currentDepth > maxDepth)
return;
Console.WriteLine($"{new string(' ', currentDepth * 2)}Directory: {root.FullName}");
try
{
foreach (DirectoryInfo dir in root.GetDirectories())
{
LimitedRecursiveSearch(dir, currentDepth + 1, maxDepth);
}
foreach (FileInfo file in root.GetFiles())
{
Console.WriteLine($"{new string(' ', (currentDepth + 1) * 2)}File: {file.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"{new string(' ', currentDepth * 2)}Error accessing {root.FullName}: {ex.Message}");
}
}
参数说明:
currentDepth:当前递归深度。maxDepth:最大允许递归深度。
4.2.2 跳过特定目录或文件的策略
有时需要忽略某些特定的目录或文件(如临时文件夹 .git 、 bin 等),可以通过过滤器实现:
private static readonly HashSet<string> SkipDirectories = new HashSet<string> { ".git", "bin", "obj" };
public void SkipDirectorySearch(DirectoryInfo root)
{
Console.WriteLine($"Directory: {root.FullName}");
try
{
foreach (DirectoryInfo dir in root.GetDirectories())
{
if (SkipDirectories.Contains(dir.Name)) continue;
SkipDirectorySearch(dir);
}
foreach (FileInfo file in root.GetFiles())
{
Console.WriteLine($"File: {file.FullName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing {root.FullName}: {ex.Message}");
}
}
过滤策略说明:
- 使用
HashSet<string>存储需要跳过的目录名,提升查找效率。 - 在递归调用前检查目录名是否在黑名单中,若在则跳过该目录。
4.3 使用LINQ进行文件集合筛选
4.3.1 LINQ查询的基本语法
LINQ(Language Integrated Query)是C#中用于查询集合的强大工具。通过LINQ,我们可以对文件集合进行过滤、排序和投影等操作。
DirectoryInfo root = new DirectoryInfo(@"C:\ExampleDir");
var query = from file in root.GetFiles("*.*", SearchOption.AllDirectories)
where file.Length > 10000 && file.Extension == ".txt"
orderby file.CreationTime descending
select new
{
FileName = file.Name,
Size = file.Length,
Created = file.CreationTime
};
代码逻辑分析:
- 获取文件集合 :
-GetFiles("*.*", SearchOption.AllDirectories):递归获取所有文件。 - 过滤条件 :
-where file.Length > 10000:文件大小大于10KB。
-file.Extension == ".txt":扩展名为.txt。 - 排序 :
-orderby file.CreationTime descending:按创建时间降序排列。 - 投影 :
-select new { ... }:选择特定属性组成匿名类型。
4.3.2 过滤、排序与投影文件集合
除了基本的LINQ查询,还可以结合 GroupBy 、 Aggregate 等高级操作对文件进行更复杂的处理。
按扩展名分组统计文件数量:
var grouped = root.GetFiles("*.*", SearchOption.AllDirectories)
.GroupBy(f => f.Extension)
.Select(g => new
{
Extension = g.Key,
Count = g.Count(),
TotalSize = g.Sum(f => f.Length)
});
foreach (var group in grouped)
{
Console.WriteLine($"{group.Extension}: {group.Count} files, Total size: {group.TotalSize} bytes");
}
输出示例:
| Extension | Count | Total size (bytes) |
|---|---|---|
| .txt | 23 | 125000 |
| .jpg | 15 | 870000 |
| .log | 7 | 95000 |
4.4 LINQ与异步处理的结合应用
4.4.1 异步加载文件列表
在处理大型目录时,同步操作可能导致UI冻结。通过异步编程模型可以提高用户体验:
public async Task<List<FileInfo>> GetFilesAsync(string path)
{
return await Task.Run(() =>
{
DirectoryInfo root = new DirectoryInfo(path);
return root.GetFiles("*.*", SearchOption.AllDirectories).ToList();
});
}
调用示例:
var files = await GetFilesAsync(@"C:\LargeDir");
var txtFiles = files.Where(f => f.Extension == ".txt").ToList();
4.4.2 使用LINQ聚合文件信息
结合LINQ与异步处理,可以高效地聚合文件信息,例如计算总大小、平均大小等:
var result = await GetFilesAsync(@"C:\Data");
double totalSize = result.Sum(f => f.Length);
double averageSize = result.Average(f => f.Length);
int fileCount = result.Count;
Console.WriteLine($"Total size: {totalSize} bytes");
Console.WriteLine($"Average size: {averageSize:F2} bytes");
Console.WriteLine($"Total files: {fileCount}");
性能对比表格:
| 方法类型 | 适用场景 | 性能表现 | 优点 |
|---|---|---|---|
| 同步LINQ | 小型目录 | 快 | 简洁直观 |
| 异步LINQ | UI交互或大型目录 | 稍慢 | 避免UI冻结,响应性强 |
| 并行LINQ | 多核CPU处理大型目录 | 极快 | 利用多线程提升性能 |
通过本章的学习,我们掌握了递归与非递归遍历目录的实现方式、目录遍历的优化策略、以及如何结合LINQ进行高效的文件筛选与处理。这些技术为后续的文件系统开发与性能优化打下了坚实的基础。
5. 路径处理、权限控制与完整示例演示
5.1 Path类的路径拼接与解析
System.IO.Path 类提供了一系列静态方法,用于操作路径字符串,而无需实际访问文件系统。这在处理路径拼接、解析、验证等场景中非常实用。
5.1.1 构建跨平台兼容的路径字符串
在C#中,推荐使用 Path.Combine() 方法来拼接路径字符串,这样可以自动根据操作系统选择正确的路径分隔符(如Windows使用 \ ,而Linux/macOS使用 / )。
string path1 = Path.Combine("C:\\Projects", "Data", "Files");
Console.WriteLine(path1); // 输出:C:\Projects\Data\Files
说明 :
-Path.Combine(params string[] paths)方法可以接受多个路径字符串进行拼接。
- 避免手动拼接路径字符串,防止因不同平台导致的路径格式问题。
5.1.2 提取文件名、扩展名与根目录
以下是一些常用的路径解析方法:
| 方法名 | 用途 | 示例 |
|---|---|---|
Path.GetFileName() |
获取文件名及扩展名 | Path.GetFileName("C:\\test\\file.txt") ➔ file.txt |
Path.GetFileNameWithoutExtension() |
获取不带扩展的文件名 | Path.GetFileNameWithoutExtension("file.txt") ➔ file |
Path.GetExtension() |
获取扩展名 | Path.GetExtension("file.txt") ➔ .txt |
Path.GetDirectoryName() |
获取路径中的目录部分 | Path.GetDirectoryName("C:\\test\\file.txt") ➔ C:\test |
Path.GetPathRoot() |
获取根目录 | Path.GetPathRoot("C:\\test\\file.txt") ➔ C:\ |
示例代码:
string fullPath = @"C:\Projects\Source\Readme.md";
Console.WriteLine("文件名:" + Path.GetFileName(fullPath)); // Readme.md
Console.WriteLine("扩展名:" + Path.GetExtension(fullPath)); // .md
Console.WriteLine("目录名:" + Path.GetDirectoryName(fullPath)); // C:\Projects\Source
Console.WriteLine("根目录:" + Path.GetPathRoot(fullPath)); // C:\
5.2 DirectorySecurity类控制目录权限
DirectorySecurity 类允许我们在C#中获取或设置目录的访问控制列表(ACL),从而实现对目录权限的精细控制。
5.2.1 获取与修改目录的访问控制列表(ACL)
我们可以使用 Directory.GetAccessControl() 方法获取目录的安全信息,并通过 Directory.SetAccessControl() 方法进行更新。
string dirPath = @"C:\SecureFolder";
DirectorySecurity dirSecurity = Directory.GetAccessControl(dirPath);
// 添加一条访问规则:允许当前用户完全控制该目录
dirSecurity.AddAccessRule(new FileSystemAccessRule(
Environment.UserName,
FileSystemRights.FullControl,
AccessControlType.Allow));
Directory.SetAccessControl(dirPath, dirSecurity);
参数说明 :
-Environment.UserName:当前用户的名称。
-FileSystemRights.FullControl:授予完全控制权限。
-AccessControlType.Allow:允许该权限。
5.2.2 添加、删除与修改用户权限
以下是一个更完整的示例,演示如何添加、删除、修改权限规则:
string dirPath = @"C:\SecureFolder";
DirectorySecurity dirSecurity = Directory.GetAccessControl(dirPath);
// 添加权限:允许用户“Domain\User”读取权限
dirSecurity.AddAccessRule(new FileSystemAccessRule(
"Domain\\User",
FileSystemRights.Read,
AccessControlType.Allow));
// 删除权限:移除用户“Domain\User”的所有权限
AuthorizationRuleCollection rules = dirSecurity.GetAccessRules(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule rule in rules)
{
if (rule.IdentityReference.Value == "Domain\\User")
{
dirSecurity.RemoveAccessRule(rule);
}
}
Directory.SetAccessControl(dirPath, dirSecurity);
说明 :
- 使用GetAccessRules()获取所有访问规则。
- 可以通过遍历规则并使用RemoveAccessRule()删除特定规则。
- 修改权限后必须调用SetAccessControl()才能生效。
5.3 异步获取目录内容(GetDirectoriesAsync)
在图形界面应用程序中,同步操作可能会导致界面冻结,因此推荐使用异步方法处理目录遍历。
5.3.1 异步操作的基本原理
虽然.NET原生的 DirectoryInfo 类没有提供异步方法,但我们可以通过 Task.Run() 封装同步方法实现异步操作。
public async Task<List<string>> GetDirectoriesAsync(string path)
{
return await Task.Run(() =>
{
try
{
return Directory.GetDirectories(path).ToList();
}
catch (Exception ex)
{
Console.WriteLine("访问出错:" + ex.Message);
return new List<string>();
}
});
}
5.3.2 在UI应用中避免界面冻结
在WPF或WinForms中使用时,可以结合 await 避免阻塞UI线程:
private async void btnLoad_Click(object sender, EventArgs e)
{
string path = txtPath.Text;
var dirs = await GetDirectoriesAsync(path);
foreach (var dir in dirs)
{
lstDirectories.Items.Add(dir);
}
}
说明 :
- 使用await确保异步执行不会阻塞主线程。
- UI控件的更新应在主线程中执行,避免跨线程异常。
5.4 完整示例代码演示目录遍历
我们来构建一个完整的控制台示例,演示如何递归遍历目录并输出所有文件。
5.4.1 控制台应用程序的实现
static void Main(string[] args)
{
string rootPath = @"C:\SampleData";
TraverseDirectory(rootPath);
}
static void TraverseDirectory(string path)
{
try
{
foreach (string dir in Directory.GetDirectories(path))
{
Console.WriteLine("目录:" + dir);
TraverseDirectory(dir); // 递归调用
}
foreach (string file in Directory.GetFiles(path))
{
Console.WriteLine(" 文件:" + file);
}
}
catch (Exception ex)
{
Console.WriteLine($"访问{path}失败:{ex.Message}");
}
}
说明 :
- 使用递归方式遍历所有子目录。
- 异常处理确保访问失败时程序不崩溃。
5.4.2 图形界面中展示目录结构
在WPF中可以使用 TreeView 控件展示目录结构:
<TreeView x:Name="treeView" />
<Button Content="加载目录" Click="LoadDirectory_Click" />
private async void LoadDirectory_Click(object sender, RoutedEventArgs e)
{
var root = new TreeViewItem { Header = "C:\\" };
await BuildTreeAsync(root, "C:\\");
treeView.Items.Add(root);
}
private async Task BuildTreeAsync(TreeViewItem parent, string path)
{
var dirs = await GetDirectoriesAsync(path);
foreach (var dir in dirs)
{
var item = new TreeViewItem { Header = Path.GetFileName(dir) };
parent.Items.Add(item);
await BuildTreeAsync(item, dir);
}
}
说明 :
- 使用TreeViewItem动态构建目录树。
- 每个节点递归加载其子节点。
5.4.3 日志记录与用户交互设计
在实际项目中,建议将操作记录输出到日志文件中,便于后期分析:
public static void Log(string message)
{
string logPath = @"C:\Logs\app.log";
string logEntry = $"{DateTime.Now}: {message}{Environment.NewLine}";
File.AppendAllText(logPath, logEntry);
}
说明 :
-File.AppendAllText()方法用于追加日志内容。
- 可扩展为使用NLog、Serilog等日志框架。
5.5 C#文件处理在实际开发中的应用
5.5.1 数据备份与同步工具开发
利用 DirectoryInfo 和 FileInfo 可以开发自动备份工具,例如:
- 定期扫描源目录中的文件
- 比较目标目录中的文件时间戳
- 复制更新过的文件并记录日志
5.5.2 文件扫描与病毒检测系统设计
结合病毒扫描引擎API,可以实现:
- 遍历指定目录下的所有文件
- 调用第三方API进行文件扫描
- 对感染文件进行隔离或删除处理
5.5.3 实际项目中的最佳实践与经验分享
- 路径处理 :始终使用
Path.Combine()和Path类方法,避免硬编码路径分隔符。 - 权限控制 :在部署前测试目标目录的权限设置,避免运行时访问失败。
- 异步处理 :对于大型目录结构,使用异步加载和并行处理提升性能。
- 日志记录 :关键操作务必记录日志,便于排查问题。
- 异常处理 :对
DirectoryNotFoundException、UnauthorizedAccessException等常见异常进行捕获和处理。
graph TD
A[开始目录遍历] --> B[读取当前目录]
B --> C{是否有子目录?}
C -->|是| D[递归遍历子目录]
C -->|否| E[读取当前目录文件]
E --> F[输出文件列表]
D --> G[输出目录结构]
G --> H[结束]
简介:在C#开发中,获取本地磁盘目录并处理文件是常见的基础操作,涉及 System.IO 命名空间中的 DirectoryInfo 、文件遍历、递归处理、权限控制等内容。本实例源码详细演示了如何使用C#实现磁盘目录的获取与遍历,包含同步与异步方法、LINQ查询、异常处理、路径操作等关键技术点。通过完整示例代码,开发者可以快速掌握文件系统的操作技巧,并应用于实际项目中。
更多推荐



所有评论(0)