1. 程式人生 > >C#路徑/檔案/目錄/I/O常見操作彙總

C#路徑/檔案/目錄/I/O常見操作彙總

 檔案操作是程式中非常基礎和重要的內容,而路徑、檔案、目錄以及I/O都是在進行檔案操作時的常見主題,這裡想把這些常見的問題作個總結,對於每個問題,儘量提供一些解決方案,即使沒有你想要的答案,也希望能提供給你一點有益的思路,如果你有好的建議,懇請能夠留言,使這些內容更加完善。
主要內容:
一、路徑的相關操作, 如判斷路徑是否合法,路徑型別,路徑的特定部分,合併路徑,系統資料夾路徑等內容;
二、相關通用檔案對話方塊,這些對話方塊可以幫助我們操作檔案系統中的檔案和目錄;
三、檔案、目錄、驅動器的操作,如獲取它們的基本資訊,獲取和設定檔案和目錄的屬性,檔案的版本資訊,
搜尋檔案和目錄,檔案判等,複製、移動、刪除、重新命名檔案和目錄;
四、讀寫檔案,包括臨時檔案,隨機檔名等;
五、對檔案系統的監視;
這一篇就先寫一下前兩部分。

一、路徑相關操作
問題1:如何判定一個給定的路徑是否有效/合法;
解決方案:通過Path.GetInvalidPathChars或Path.GetInvalidFileNameChars方法獲得非法的路徑/檔名字元,可以
根據它來判斷路徑中是否包含非法字元;

問題2:如何確定一個路徑字串是表示目錄還是檔案;
解決方案:
1、使用Directory.Exists或File.Exist方法,如果前者為真,則路徑表示目錄;如果後者為真,則路徑表示檔案;
2、上面的方法有個缺點就是不能處理那些不存在的檔案或目錄。這時可以考慮使用Path.GetFileName方法獲得
其包含的檔名,如果一個路徑不為空,而檔名為空那麼它表示目錄,否則表示檔案;

問題3:如何獲得路徑的某個特定部分(如檔名、副檔名等);
解決方案:
下面是幾個相關方法:
Path.GetDirectoryName :返回指定路徑字串的目錄資訊;
Path.GetExtension : 返回指定的路徑字串的副檔名;
Path.GetFileName : 返回指定路徑字串的檔名和副檔名;
Path.GetFileNameWithoutExtension :返回不具有副檔名的路徑字串的檔名;
Path.GetPathRoot :獲取指定路徑的根目錄資訊;
(更多內容還請參考MSDN)

問題4:如何準確地合併兩個路徑而不用去擔心那個煩人的”\”字元;
解決方案:
使用Path.Combine方法,它會幫你處理煩人的”\”;
問題5:如何獲得系統目錄的的路徑(如桌面,我的文件,臨時資料夾等);
解決方案:
主要是使用System. Environment類的相關屬性和方法:
Environment. SystemDirectory屬性:獲取系統目錄的完全限定路徑;
Environment. GetFolderPath方法:該方法接受的引數型別為Environment.SpecialFolder列舉,
通過這個方法可以獲得大量系統資料夾的路徑,如我的電腦,我的電腦,桌面,系統目錄等;
(更多內容還請參考MSDN);
Path.GetTempPath方法:返回當前系統的臨時資料夾的路徑;

問題6:如何判斷一個路徑是絕對路徑還是相對路徑;
解決方案:
使用Path.IsPathRooted方法;

問題7:如何讀取或設定當前目錄;
解決方案:
使用Directory類的GetCurrentDirectory和SetCurrentDirectory方法;

問題8:如何使用相對路徑;
解決方案:
設定當前目錄後(見問題7),就可以使用相對路徑了。對於一個相對路徑,我們可以
使用Path.GetFullPath方法獲得它的完全限定路徑(絕對路徑)。

注意:如果打算使用相對路徑,建議你將工作目錄設定為各個互動檔案的共同起點,否則可能會引入
一些不易發現的安全隱患,被惡意使用者利用來訪問系統檔案。

更多內容:
通常我們可以使用System.IO.Path類來處理路徑。該類提供了一套方法和屬性用於對包含檔案或目錄路徑資訊的字串執行操作,這些操作是以跨平臺的方式執行的,而這些方法和屬性都是靜態的。

注意路徑僅僅是提供檔案或目錄位置的字串。路徑不必指向磁碟上的位置,例如,路徑可以對映到記憶體中或裝置上的位置。路徑的準確格式是由當前平臺確定的。例如,在某些系統上,路徑可以驅動器號或卷號開始,而此元素在其他系統中是不存在的。在某些系統上,檔案路徑可以包含副檔名,副檔名指示在檔案中儲存的資訊的型別。副檔名的格式是與平臺相關的;例如,某些系統將副檔名的長度限制為 3 個字元,而其他系統則沒有這樣的限制。當前平臺還確定用於分隔路徑中各元素的字符集,以及確定在指定路徑時不能使用的字符集。因為這些差異,所以 Path 類的欄位以及 Path 類的某些成員的準確行為是與平臺相關的。

路徑可以包含絕對或相對位置資訊。絕對路徑完整指定一個位置:檔案或目錄可被唯一標識,而與當前位置無關。相對路徑指定部分位置:當定位用相對路徑指定的檔案時,當前位置用作起始點。

Path類的大多數成員不與檔案系統互動,並且不驗證路徑字串指定的檔案是否存在。修改路徑字串的Path 類成員(例如 ChangeExtension)對檔案系統中檔案的名稱沒有影響。但Path成員確實驗證指定路徑字串的內容;並且如果字串包含在路徑字串中無效的字元(如 InvalidPathChars 中的定義),則引發 ArgumentException異常。例如,在基於 Windows 的桌面平臺上,無效路徑字元可能包括引號 (")、小於號 (<)、大於號 (>)、管道符號 (|)、退格 (\b)、空 (\0) 以及從 16 到 18 和從 20 到 25的 Unicode 字元。

Path 類的成員使您可以快速方便地執行常見操作,例如確定副檔名是否是路徑的一部分,以及將兩個字串組合成一個路徑名。

多數情況下,如果這些方法接收了無效的路徑會丟擲異常,但如果路徑名是因為包含了萬用字元(*或?)從而無效,則不會丟擲異常(可以使用GetInvalidPathChars方法來非法的路徑字元)。我們可以根據該原則判斷一個路徑是否合法。

二、相關的通用檔案對話方塊
1、資料夾瀏覽對話方塊(FolderBrowserDialog類)
使用者可以通過該對話方塊瀏覽、新建並選擇資料夾

主要屬性:
Description:樹檢視控制元件上顯示的說明文字,如上圖中的”選擇要進行計算的目錄”;
RootFolder:獲取或設定從其開始瀏覽的根資料夾,如上圖中設定的我的電腦(預設為桌面);
SelectedPath:獲取或設定使用者選定的路徑,如果設定了該屬性,開啟對話方塊時會定位到指定路徑,預設為根資料夾,關閉對話方塊時根據該屬性獲取使用者使用者選定的路徑;
ShowNewFolderButton:獲取或設定是否顯示新建對話方塊按鈕;

主要方法:
ShowDialog:開啟該對話方塊,返回值為DialogResult型別值,如果為DialogResult.OK,則可以由SelectedPath屬性獲取使用者選定的路徑;

dlgOpenFolder.Description = "選擇要進行計算的目錄";
     dlgOpenFolder.RootFolder = Environment.SpecialFolder.MyComputer;
     dlgOpenFolder.ShowNewFolderButton = true;
     DialogResult result = dlgOpenFolder.ShowDialog(this);
     if (result == DialogResult.OK)
     {
         txtDirPath.Text = dlgOpenFolder.SelectedPath;
     }


2、開啟檔案對話方塊(OpenFileDialog類)
使用者可以通過該對話方塊選擇一個檔案

主要屬性:
CheckFileExists:該值指示如果使用者指定不存在的檔名,對話方塊是否顯示警告;
FileName(s):獲取或設定一個包含在檔案對話方塊中選定的檔名的字串;
Filter:獲取或設定對話方塊的檔案型別列表;
FilterIndex:對話方塊的檔案型別列表的索引(基於1的);
InitialDirectory:獲取或設定檔案對話方塊顯示的初始目錄;
Multiselect:該值指示對話方塊是否允許選擇多個檔案;
ShowReadOnly:該值指示對話方塊是否包含只讀複選框;
Title:獲取或設定檔案對話方塊標題;
主要方法:
OpenFile:開啟使用者選定的具有隻讀許可權的檔案;
ShowDialog:開啟該模式對話方塊;

dlgOpenFile.Title = "開啟原始檔";
     dlgOpenFile.InitialDirectory = @"C:\Inetpub\";
     dlgOpenFile.Filter = "文字檔案 (*.txt)|*.txt|所有檔案 (*.*)|*.*";
     dlgOpenFile.FilterIndex = 2;
     dlgOpenFile.ShowReadOnly = true;
     DialogResult dr = dlgOpenFile.ShowDialog();
     if (dr == DialogResult.OK)
     {
         string fileName = dlgOpenFile.FileName;
     }

3、儲存檔案對話方塊(SaveFileDialog類)
使用者可以通過該對話方塊儲存一個檔案



主要屬性:
大部分與開啟檔案對話方塊類似,此處略過,下面幾個值得注意:
CreatePrompt:該值指示如果使用者指定不存在的檔案,是否提示使用者允許建立該檔案;
OverwritePrompt:該值指示如果使用者指定的檔名已存在,對話方塊是否顯示警告;
主要方法:
OpenFile:開啟使用者選定的具有讀/寫許可權的檔案;
ShowDialog:開啟該模式對話方塊;
示例程式碼:

dlgSaveFile.Title = "開啟目標檔案";
     dlgSaveFile.InitialDirectory = @"C:\Inetpub\";
     dlgSaveFile.Filter = "文字檔案 (*.txt)|*.txt|所有檔案 (*.*)|*.*";
     dlgSaveFile.FilterIndex = 2;
     DialogResult dr = dlgSaveFile.ShowDialog();
     if (dr == DialogResult.OK)
     {
         string fileName = dlgSaveFile.FileName;
     }


至此,我們操作的都只是路徑,要知道,這些路徑僅僅是字串,還沒有涉及到檔案系統中的真實檔案。

三、檔案和目錄相關操作
檔案和目錄操作涉及的類主要是:FileInfo,DirectoryInfo,DriveInfo,可以認為它們的一個例項對應著一個檔案、目錄、驅動器。它們的用法類似,一般是將檔案、目錄或驅動器的路徑作為引數傳遞給相應的建構函式建立一個例項,然後訪問它們的屬性和方法。
注意下面幾點:
FileInfo 類和 DirectoryInfo 類都繼承自抽象類 FileSystemInfo , FileSystemInfo 類定義了一些通用的屬性,如 CreationTime 、 Exists 等。但 DriveInfo 類沒有繼承 FileSystemInfo 類,所以它也就沒有上面提到的那些通用屬性了。

FileInfo 類和 DirectoryInfo 類的物件公開的屬性值都是第一次查詢時獲取的值,如果在以此查詢之後檔案或目錄發生了改動,就必須呼叫它們的 Refresh 方法來更新這些屬性。但 DriveInfo 則無需這麼做,它的屬性每次都會讀取檔案系統最新的資訊。

在建立檔案、目錄或驅動器的例項時,如果使用了一個不存在的路徑,並不會報錯,這是你得到一個物件,該物件表示一個並不存在的實體,這意味著它的 Exists 屬性(對於 DriveInfo 來說是 IsReady 屬性)值為 false 。你仍然可以操作該實體,但如果嘗試其它的大多數屬性,就會引發相應的 FileNotFoundException 、 DirectoryNotFoundException 或 DriveNotFoundException 異常。

另外,還可以使用 File / Directory 類,這兩個類的成員都是靜態方法,所以如果只想執行一個操作,那麼使用 File/Directory 中的靜態方法的效率比使用相應的 FileInfo / DirectoryInfo中的 例項方法可能更高。所有的 File / Directory 方法都要求當前所操作的檔案 / 目錄的路徑。 注意: File / Directory 類的靜態方法對所有方法都執行安全檢查。如果打算多次重用某個物件,可考慮改用 FileInfo / DirectoryInfo 的相應例項方法,因為並不總是需要安全檢查。

下面是一些常見的問題:
問題1:如何獲取指定檔案的基本資訊;
解決方案:可以使用FileInfo類的相關屬性:
FileInfo.Exists:獲取指定檔案是否存在;
FileInfo.Name,FileInfo.Extensioin:獲取檔案的名稱和副檔名;
FileInfo.FullName:獲取檔案的全限定名稱(完整路徑);
FileInfo.Directory:獲取檔案所在目錄,返回型別為DirectoryInfo;
FileInfo.DirectoryName:獲取檔案所在目錄的路徑(完整路徑);
FileInfo.Length:獲取檔案的大小(位元組數);
FileInfo.IsReadOnly:獲取檔案是否只讀;
FileInfo.Attributes:獲取或設定指定檔案的屬性,返回型別為FileAttributes列舉,可以是多個值的組合(見問題2);
FileInfo.CreationTime、FileInfo.LastAccessTime、FileInfo.LastWriteTime:分別用於獲取檔案的建立時間、訪問時間、修改時間;
(更多內容還請參考MSDN)

問題2:如何獲取和設定檔案的屬性,比如只讀、存檔、隱藏等;
解決方案:
使用FileInfo.Attributes屬性可以獲取和設定檔案的屬性,該屬性型別為FileAttributes列舉,該列舉的每個值表示一種屬性,FileAttributes列舉具有屬性(Attribute)FlagsAttribute,所以該列舉的值可以進行組合,也就是一個檔案可以同時擁有多個屬性。下面看看具體的做法:
獲取屬性,比如判斷一個檔案是否是隻讀的:

// 當檔案具有其它屬性時,這種做法會失敗
     if (file.Attributes == FileAttributes.ReadOnly)
     {
         chkReadonly.Checked = true;
     }

     // 這種寫法就不會有問題了,它只檢查只讀屬性
     if ((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
     {
         chkReadonly.Checked = true;
     }


設定屬性,比如新增和移除一個檔案的只讀屬性:

if (chkReadonly.Checked)
     {
         // 新增只讀屬性
         file.Attributes |= FileAttributes.ReadOnly;
     }
     else
     {
         // 移除只讀屬性
         file.Attributes &= ~FileAttributes.ReadOnly;
     }

問題3:如何獲取檔案的版本資訊(比如版本號,版權宣告,公司名稱等);
解決方案:
使用FileVersionInfo類,該類有大量的版本資訊相關的屬性。通過它的靜態方法GetVersionInfo獲得該類的一個例項,然後就可以訪問指定檔案的版本資訊了,非常方便。如FileVersion表示檔案版本號,LegalCopyright表示指定檔案的版權宣告,CompanyName表示指定檔案的公司名稱。(更多內容還請參考MSDN)

問題4:如何判斷兩個檔案的內容是否相同(精確匹配);
解決方案:
使用System.security.Cryptography.HashAlgorithm類為每個檔案生成一個雜湊碼,然後比較兩個雜湊碼是否一致。
在比較檔案內容的時候可以採用好幾種方法。例如,檢查檔案的某一特定部分是否一致;如果願意,你甚至可以逐位元組讀取檔案,逐位元組進行比較。這兩種方法都是可以的,但在某些情況下,還是使用雜湊碼演算法更為方便。
該演算法為一個檔案生成一個小的(通常約為20位元組)二進位制”指紋”(binary fingerprint)。從統計學角度看,不同的檔案不可能生成相同的雜湊碼。事實上,即使是一個很小的改動(比如,修改了原始檔中的一個bit),也會有50%的機率來改變雜湊碼中的每一個bit。因此,雜湊碼常常用於資料安全方面。
要生成一個雜湊碼,你必須首先建立一個HashAlgorithm物件,而這通常是呼叫HashAlgorithm.Create方法來完成的;然後呼叫HashAlgorithm.ComputeHash方法,它會返回一個儲存雜湊碼的位元組陣列。程式碼如下:

/// <summary>
     /// 判斷兩個檔案內容是否一致
     /// </summary>
     public static bool IsFilesEqual(string fileName1, string fileName2)
     {
         using (HashAlgorithm hashAlg = HashAlgorithm.Create())
         {
             using (FileStream fs1 = new FileStream(fileName1, FileMode.Open), fs2 = new FileStream(fileName2, FileMode.Open))
             {
                 byte[] hashBytes1 = hashAlg.ComputeHash(fs1);
                 byte[] hashBytes2 = hashAlg.ComputeHash(fs2);

                 // 比較雜湊碼
                 return (BitConverter.ToString(hashBytes1) == BitConverter.ToString(hashBytes2));
             }
         }
     }


問題5:如何獲取指定目錄的基本資訊;
解決方案:可以使用DirectoryInfo類的相關屬性和方法:
DirectoryInfo.Exists:獲取指定目錄是否存在;
DirectoryInfo.Name:獲取目錄的名稱;
DirectoryInfo.FullName:獲取目錄的全限定名稱(完整路徑);
DirectoryInfo.Attributes:獲取或設定指定目錄的屬性,返回型別為FileAttributes列舉,可以是多個值的組合;
DirectoryInfo.CreationTime、FileInfo.LastAccessTime、FileInfo.LastWriteTime:分別用於獲取目錄的建立時間、訪問時間、修改時間;
DirectoryInfo.Parent:獲取目錄的上級目錄,返回型別為DirectoryInfo;
DirectoryInfo.Root:獲取目錄的根目錄,返回型別為DirectoryInfo;


問題6:如何獲取指定目錄包含的檔案和子目錄;
解決方案:
DirectoryInfo.GetFiles():獲取目錄中(不包含子目錄)的檔案,返回型別為FileInfo[],支援萬用字元查詢;
DirectoryInfo.GetDirectories():獲取目錄(不包含子目錄)的子目錄,
返回型別為DirectoryInfo[],支援萬用字元查詢;
DirectoryInfo. GetFileSystemInfos():獲取指定目錄下(不包含子目錄)的檔案和子目錄,
返回型別為FileSystemInfo[],支援萬用字元查詢;


問題7:如何獲得指定目錄的大小;
解決方案:
檢查目錄內的所有檔案,利用FileInfo.Length屬性獲取每個檔案的大小,然後進行合計,然後使用遞迴演算法處理所有的子目錄的檔案,參考下面程式碼:

/// <summary>
     /// 計算一個目錄的大小
     /// </summary>
     /// <param name="di">指定目錄</param>
     /// <param name="includeSubDir">是否包含子目錄</param>
     /// <returns></returns>
     private long CalculateDirSize(DirectoryInfo di, bool includeSubDir)
     {
         long totalSize = 0;

         // 檢查所有(直接)包含的檔案
         FileInfo[] files = di.GetFiles();
         foreach (FileInfo file in files)
         {
             totalSize += file.Length;
         }

         // 檢查所有子目錄,如果includeSubDir引數為true
         if (includeSubDir)
         {
             DirectoryInfo[] dirs = di.GetDirectories();
             foreach (DirectoryInfo dir in dirs)
             {
                 totalSize += CalculateDirSize(dir, includeSubDir);
             }
         }

         return totalSize;
     }

問題8:如何使用萬用字元搜尋指定目錄內的所有檔案;
解決方案:
使用DirectoryInfo.GetFiles方法的過載版本,它可以接受一個過濾表示式,返回FileInfo陣列,另外它的引數還可以指定是否對子目錄進行查詢。如:


dir.GetFiles("*.txt", SearchOption.AllDirectories);
問題9:如何複製、移動、重新命名、刪除檔案和目錄;
解決方案:使用FileInfo和DirectoryInfo類。
下面是FileInfo類的相關方法:
FileInfo.CopyTo:將現有檔案複製到新檔案,其過載版本還允許覆蓋已存在檔案;
FileInfo.MoveTo:將指定檔案移到新位置,並提供指定新檔名的選項,所以可以用來重新命名檔案(而不改變位置); FileInfo.Delete:永久刪除檔案,如果檔案不存在,則不執行任何操作;
FileInfo.Replace:使用當前FileInfo物件對應檔案的內容替換目標檔案,而且指定另一個檔名作為被替換檔案的備份,微軟考慮實在周到。

下面是DirectoryInfo類的相關方法:
DirectoryInfo.Create:建立指定目錄,如果指定路徑中有多級目錄不存在,該方法會一一建立;
DirectoryInfo.CreateSubdirectory:建立當前物件對應的目錄的子目錄;
DirectoryInfo.MoveTo:將目錄(及其包含的內容)移動至一個新的目錄,也可用來重新命名目錄;
DirectoryInfo.Delete:刪除目錄(如果它存在的話)。如果要刪除一個包含子目錄的目錄,要使用它的過載版本,以指定遞迴刪除。


注意到了沒有?DirectoryInfo類少了一個CopyTo方法,不過我們可以通過遞迴來實現這個功能:


/// <summary>
     /// 複製目錄到目標目錄
     /// </summary>
     /// <param name="source">源目錄</param>
     /// <param name="destination">目標目錄</param>
     public static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
     {
         // 如果兩個目錄相同,則無須複製
         if (destination.FullName.Equals(source.FullName))
         {
             return;
         }

         // 如果目標目錄不存在,建立它
         if (!destination.Exists)
         {
             destination.Create();
         }

         // 複製所有檔案
         FileInfo[] files = source.GetFiles();
         foreach (FileInfo file in files)
         {
             // 將檔案複製到目標目錄
             file.CopyTo(Path.Combine(destination.FullName, file.Name), true);
         }

         // 處理子目錄
         DirectoryInfo[] dirs = source.GetDirectories();
         foreach (DirectoryInfo dir in dirs)
         {
             string destinationDir = Path.Combine(destination.FullName, dir.Name);

             // 遞迴處理子目錄
             CopyDirectory(dir, new DirectoryInfo(destinationDir));
         }
     }



問題10:如何獲得計算機的所有邏輯驅動器;
解決方案:使用DriveInfo類(需要.NET 2.0)
DriveInfo.GetDrives():獲得計算機的所有邏輯驅動器,返回型別為DriveInfo[];


問題11:如何獲取指定驅動器的資訊;
解決方案:
DriveInfo.Name:獲取驅動器的名稱(如C:\);
DriveInfo.DriveType:獲取驅動器的型別(如Fixed,CDRom,Removable,Network等);
DriveInfo.DriveFormat:獲取驅動器的格式(如NTFS,FAT32,CDFS,UDF等);
DriveInfo.IsReady:獲取驅動器是否已準備好,比如CD是否已放入CD驅動器,如果驅動器沒有準備好,訪問其資訊會引發IOException型別異常;
DriveInfo.AvailableFreeSpace:獲取驅動器的可用空間;
DriveInfo.TotalFreeSpace:獲取驅動器總的可用空間,它與AvailableFreeSpace的不同在於AvailableFreeSpace會磁碟配額的設定;
DriveInfo.TotalSize:獲取驅動器總的空間;
DriveInfo.RootDirectory:獲得驅動器的根目錄(DirectoryInfo型別);

至此,我們已經瞭解了檔案和目錄相關的一些基本操作。