HOW TO:逐一查看目錄樹狀結構 (C# 程式設計手冊)
「逐一查看目錄樹狀結構」一詞,代表存取指定根資料夾下每個巢狀子目錄中任意深度的每個檔案。 您不需要開啟每個檔案。 只需要以 string 方式擷取檔案或子目錄的名稱,或者以 System.IO.FileInfo 或 System.IO.DirectoryInfo 物件的型式擷取其他的資訊。
注意事項 |
---|
在 Windows 中可以交替使用「目錄」和「資料夾」這兩個詞彙。 大部分的文件和使用者介面文字是使用「資料夾」,但 .NET Framework 類別庫是使用「目錄」一詞。 |
在最簡單的情況下,當您確定具有指定根資料夾下所有目錄的存取權限時,則可以使用 System.IO.SearchOption.AllDirectories 旗標。 這個旗標會傳回符合指定模式的所有巢狀子目錄。 下列範例顯示如何使用這個旗標。
root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);
這個方式有個缺點,就是指定根資料夾下如果有任何一個子目錄造成 DirectoryNotFoundException 或 UnauthorizedAccessException,整個方法就會失敗並且不會傳回任何目錄。 同樣的情形也適用於使用 GetFiles 方法時。 如果必須對特定子資料夾處理這些例外狀況 (Exception),您必須手動走遍目錄樹狀結構,如下列範例所示。
手動走遍目錄樹狀結構時,您可以先處理子目錄 (「前序走訪」(Pre-order Traversal)),或是先處理檔案 (「後序走訪」(Post-order Traversal))。 如果執行的是前序走訪,您要先走遍目前資料夾下的整個樹狀結構,才逐一查看直接位於資料夾本身中的檔案。 本文件稍後的範例會執行後序走訪,但您只要簡單修改一下即可執行前序走訪。
另一個選擇為是否要使用遞迴或堆疊式走訪。 本文件稍後的範例會顯示這兩種方式。
如果必須對檔案和資料夾執行各式各樣的作業,您可以藉由將作業重構到個別函式中以模組化這些範例,這樣就可以使用單一委派 (Delegate) 來叫用這些函式。
注意事項 |
---|
NTFS 檔案系統可以包含的「重新分析點」(Reparse Point) 型式有「連接點交叉點」(Junction Point)、「符號連結」(Symbolic Link) 和「永久連結」(Hard Link)。 而 GetFiles 和 GetDirectories 這類的 .NET Framework 方法,不會傳回重新分析點下的任何子目錄。 這個行為可以在兩個重新分析點彼此參考時,預防進入無限迴圈的風險。 一般而言,處理重新分析點要特別謹慎,避免不小心修改或刪除檔案。 如果需要精確控制重新分析點,請使用平台叫用或機器碼,直接呼叫適當的 Win32 檔案系統方法。 |
範例
下列範例會示範如何使用遞迴走遍目錄樹狀結構。 遞迴是很簡潔的方式,但在目錄樹狀結構很大且巢狀結構很深時,很可能造成堆疊溢位 (Stack Overflow) 例外狀況。
這裡所處理的特定例外狀況,以及對每個檔案和資料夾所執行的特定動作,都僅是提供做為範例用途。 您應該針對您的特定需求修改這個程式碼。 如需詳細資訊,請參閱程式碼中的註解。
public class RecursiveFileSearch
{
static System.Collections.Specialized.StringCollection log = new System.Collections.Specialized.StringCollection();
static void Main()
{
// Start with drives if you have to search the entire computer.
string[] drives = System.Environment.GetLogicalDrives();
foreach (string dr in drives)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(dr);
// Here we skip the drive if it is not ready to be read. This
// is not necessarily the appropriate action in all scenarios.
if (!di.IsReady)
{
Console.WriteLine("The drive {0} could not be read", di.Name);
continue;
}
System.IO.DirectoryInfo rootDir = di.RootDirectory;
WalkDirectoryTree(rootDir);
}
// Write out all the files that could not be processed.
Console.WriteLine("Files with restricted access:");
foreach (string s in log)
{
Console.WriteLine(s);
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key");
Console.ReadKey();
}
static void WalkDirectoryTree(System.IO.DirectoryInfo root)
{
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;
// First, process all the files directly under this folder
try
{
files = root.GetFiles("*.*");
}
// This is thrown if even one of the files requires permissions greater
// than the application provides.
catch (UnauthorizedAccessException e)
{
// This code just writes out the message and continues to recurse.
// You may decide to do something different here. For example, you
// can try to elevate your privileges and access the file again.
log.Add(e.Message);
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
}
if (files != null)
{
foreach (System.IO.FileInfo fi in files)
{
// In this example, we only access the existing FileInfo object. If we
// want to open, delete or modify the file, then
// a try-catch block is required here to handle the case
// where the file has been deleted since the call to TraverseTree().
Console.WriteLine(fi.FullName);
}
// Now find all the subdirectories under this directory.
subDirs = root.GetDirectories();
foreach (System.IO.DirectoryInfo dirInfo in subDirs)
{
// Resursive call for each subdirectory.
WalkDirectoryTree(dirInfo);
}
}
}
}
下列範例會示範如何在不使用遞迴的情況下,逐一查看目錄樹狀結構中的檔案和資料夾。 這項技術使用泛型 Stack<T> 集合型別,這是屬於後進先出 (LIFO) 堆疊。
這裡所處理的特定例外狀況,以及對每個檔案和資料夾所執行的特定動作,都僅是提供做為範例用途。 您應該針對您的特定需求修改這個程式碼。 如需詳細資訊,請參閱程式碼中的註解。
public class StackBasedIteration
{
static void Main(string[] args)
{
// Specify the starting folder on the command line, or in
// Visual Studio in the Project > Properties > Debug pane.
TraverseTree(args[0]);
Console.WriteLine("Press any key");
Console.ReadKey();
}
public static void TraverseTree(string root)
{
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = System.IO.Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable
// to ignore the exception and continue enumerating the remaining files and
// folders. It is also possible (but unlikely) that a DirectoryNotFound exception
// will be raised. This will happen if currentDir has been deleted by
// another application or thread after our call to Directory.Exists. The
// choice of which exceptions to catch depends entirely on the specific task
// you are intending to perform and also on how much you know with certainty
// about the systems on which this code will run.
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
string[] files = null;
try
{
files = System.IO.Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
}
catch (System.IO.FileNotFoundException e)
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
Console.WriteLine(e.Message);
continue;
}
}
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
}
}
藉由測試每個資料夾,判斷應用程式是否有權限開啟資料夾的作業,通常都非常耗時。 因此,程式碼範例只有在 try/catch 區塊中封入該部分的作業。 您可以修改 catch 區塊,好讓您在存取資料夾遭到拒絕時,可以嘗試升級權限,然後再次存取資料夾。 通常,您只需要攔截那些您可以處理的例外狀況,不要讓應用程式處於未知狀態。
如果必須儲存目錄樹狀結構的內容,不管是存於記憶體或磁碟中,最佳的選項就是只要儲存每個檔案的 FullName 屬性 (型別為 string)。 然後,您可以視需要使用這個字串建立新 FileInfo 或 DirectoryInfo 物件,或者是開啟任何需要其他處理的檔案。
穩固程式設計
建立穩固的檔案逐一查看程式碼,必須考慮許多檔案系統的複雜度。 如需詳細資訊,請參閱 NTFS 技術參考 (英文)。