文件操作是程序中非常基礎和重要的內容,而路徑、文件、目錄以及I/O都是在進行文件操作時的常見主題,這里想把這些常見的問題作個總結,對于每個問題,盡量提供一些解決方案,即使沒有你想要的答案,也希望能提供給你一點有益的思路,如果你有好的建議,懇請能夠留言,使這些內容更加完善。
??? 主要內容:
??? 一、路徑的相關操作,如判斷路徑是否合法,路徑類型,路徑的特定部分,合并路徑,系統文件夾路徑等內容;
??? 二、相關通用文件對話框,這些對話框可以幫助我們操作文件系統中的文件和目錄;
??? 三、文件和目錄操作,如復制、移動、刪除、重命名,文件的版本信息,文件判等、搜索,讀寫文件等;
??? 四、讀寫文件,對文件系統的監視;
??? 五、其它,如臨時文件,隨機文件名等;

??? 第一篇-路徑的相關操作和通用文件對話框的使用

??? 第二篇-文件和目錄的相關操作

??? 這一篇將介紹第四、五部分。

??? 文件讀寫相關類介紹:
??? 文件讀寫操作涉及的類主要是:
??? MarshalByRefObject 類:允許在支持遠程處理的應用程序中跨應用程序域邊界訪問對象;
??? BinaryReader 類:用特定的編碼將基元數據類型讀作二進制值。
??? BinaryWriter 類: 以二進制形式將基元類型寫入流,并支持用特定的編碼寫入字符串。
??? Stream 類: 提供字節序列的一般視圖。
??? FileStream類:公開以文件為主的 Stream,既支持同步讀寫操作,也支持異步讀寫操作。
??? MemoryStream 類:創建其支持存儲區為內存的流。
??? BufferedStream 類:給另一流上的讀寫操作添加一個緩沖層。
??? TextReader 類:表示可讀取連續字符系列的閱讀器。
??? TextWriter 類:表示可以編寫一個有序字符系列的編寫器。
??? StreamReader 類:實現一個 TextReader,使其以一種特定的編碼從字節流中讀取字符。
??? StreamWriter 類:實現一個 TextWriter,使其以一種特定的編碼向流中寫入字符。
??? StringReader 類:實現從字符串進行讀取的 TextReader。
??? StringWriter 類:實現一個用于將信息寫入字符串的 TextWriter。該信息存儲在基礎StringBuilder中。
??? 在使用它們之前最好能了解它們的繼承關系,有助于作出最合適的選擇:

??? 另外還要注意一下FileInfo和File類的一些方法,如Create,CreateText,Open等,有時也會帶來方便。
??? 這些類的內容比較繁多,更多內容還請參考MSDN。?

??? 下面是一些常見的問題及其解決方案:
??? 問題1:如何讀寫文本文件(并考慮不同的編碼類型)
??? 解決方案:
??? 創建一個FileStream對象用以引用該文件。要寫入文件,將FileStream對象封裝在StreamWriter對象中,使用其重載了的Write方法;要讀取文件,將FileStream對象封裝在StreamReader對象中,使用其Read或ReadLine方法;
??? .NET Framework允許通過StreamWriter和StreamReader類操作任何流來讀寫文本文件。當使用StreamWriter類寫入數據時,調用它的Write方法,該方法在重載后可以支持所有常見的C#數據類型,包括字符串、字符、整數、浮點數以及十進制數等。但Write方法總會將的得到的數據轉換為文本,如果希望將這些文本轉換回原來的數據類型,應使用WriteLine方法,以確保每個值都處于單獨的一行上。
??? 字符串的表現形式取決于你使用的編碼,最常見的編碼類型包括下面幾種:ASCII,UTF-16,UTF-7,UTF-8。
??? .NET Framework在System.Text命名空間中為每種編碼類型提供了一個類。在使用StreamWriter和StreamReader類時,可以指定需要的編碼類型,或者使用默認的UTF-8。
??? 而在讀取文本文件時,則要使用StreamReader類的Read或ReadLine方法。Read方法讀取單個字符或者指定個數的字符,返回類型為字符或字符數組;ReadLine方法則返回包含整行內容的字符串;ReadToEnd方法從當前位置讀取至流的結尾。
??? (更多內容還請參考MSDN)
??? 寫入文本文件的示例:

????using?(FileStream?fs?=?new?FileStream(fileName,?FileMode.Create))
????{
????????
//?創建一個StreamWriter對象,使用UTF-8編碼格式
????????using?(StreamWriter?writer?=?new?StreamWriter(fs,?Encoding.UTF8))
????????{
????????????
//?分別寫入十進制數,字符串和字符類型的數據
????????????writer.WriteLine(123.45M);
????????????writer.WriteLine(
"String?Data");
????????????writer.WriteLine(
'A');
????????}
????}
???
??? 讀取文本文件的示例:
????//?以只讀模式打開一個文本文件
????using?(FileStream?fs?=?new?FileStream(fileName,?FileMode.Open))
????{
????????
using?(StreamReader?reader?=?new?StreamReader(fs,?Encoding.UTF8))
????????{
????????????
string?text?=?string.Empty;

????????????
while(!reader.EndOfStream)
????????????{
????????????????text?
=?reader.ReadLine();
????????????????txtMessage.Text?
+=?text?+?Environment.NewLine;
????????????}
????????}
????}

??? 問題2:如何讀寫二進制文件(使用強數據類型)
??? 解決方案:
??? 創建一個FileStream對象用以引用該文件。要寫入文件,將FileStream對象封裝在BinaryWriter對象中,使用其重載了的Write方法;要讀取文件,將FileStream對象封裝在BinaryReader對象中,使用相應數據類型的Read方法。?????????????
??? .NET Framework允許通過BinaryWriter和BinaryReader類操作任何流來讀寫二進制數據。當使用BinaryWriter類寫入數據時,調用它的Write方法,該方法在重載后可以支持所有常見的C#數據類型,包括字符串、字符、整數、浮點數以及十進制數等,然后數據會被編碼為一系列字節寫入文件,也可以配置該過程中的編碼類型。
??? 在使用二進制文件時,一定要特別注意其中的數據類型。當你讀取數據時,一定要使用BinaryReader類的某種強類型的Read方法。例如,要讀取字符串,要使用ReadString方法。(BinaryWriter在寫入二進制文件時總會記錄字符串的長度以避免任何可能的錯誤)
??? 寫入文件的示例:
????using?(FileStream?fs?=?new?FileStream(fileName,?FileMode.Create))
????{
????????
using?(BinaryWriter?writer?=?new?BinaryWriter(fs))
????????{
????????????
//?寫入十進制數,字符串和字符
????????????writer.Write(234.56M);
????????????writer.Write(
"String");
????????????writer.Write(
'!');
????????}
????}

??? 讀取文件的示例:
????//?以只讀模式打開一個二進制文件
????using?(FileStream?fs?=?new?FileStream(fileName,?FileMode.Open))
????{
????????
using?(StreamReader?sr?=?new?StreamReader(fs))
????????{
????????????MessageBox.Show(
"全部數據:"?+?sr.ReadToEnd());

????????????fs.Position?
=?0;
????????????
using?(BinaryReader?reader?=?new?BinaryReader(fs))
????????????{
????????????????
//?選用合適的數據類型讀取數據
????????????????string?message?=?reader.ReadDecimal().ToString()?+?Environment.NewLine;
????????????????message?
+=?reader.ReadString()?+?Environment.NewLine;
????????????????message?
+=?reader.ReadChar().ToString();
????????????????MessageBox.Show(message);
????????????}
????????}
????}

??? 問題3:如何異步讀取文件;
??? 解決方案:
??? 有時你需要讀取一個文件但又不希望影響程序的執行。常見的情況是讀取一個存儲在網絡驅動器上的文件。?????????
??? FileStream提供了對異步操作的基本支持,即它的BeginRead和EndRead方法。使用這些方法,可以在.NET Framework線程池提供的線程中讀取一個數據塊,而無須直接與System.Threading命名空間中的線程類打交道。
??? 采用異步方式讀取文件時,可以選擇每次讀取數據的大小。根據情況的不同,你可能會每次讀取很小的數據(比如,你要將數據逐塊拷貝至另一個文件),也可能是一個相對較大的數據(比如,在程序邏輯開始之前需要一定數量的數據)。在調用BeginRead時指定要讀取數據塊的大小,同時傳入一個緩沖區(buffer)以存放數據。因為BeginRead和EndRead需要訪問很多相同的信息,如FileStream,buffer,數據塊大小等,因此將這些內容封裝一個單獨的類當中是一個好主意。
??? 下面這個類就是一個簡單的示例。AsyncProcessor類提供了StartProcess方法,調用它開始讀取,每次讀取操作結束,OnCompletedRead回調函數會被觸發,此時可以處理數據,如果還有剩余數據,則開始一個新的讀取操作。默認情況下,AsyncProcessor類每次讀取2KB數據。
????class?AsyncProcessor
????{
????????
private?Stream?inputStream;

????????
//?每次讀取塊的大小
????????private?int?bufferSize?=?2048;

????????
public?int?BufferSize
????????{
????????????
get?{?return?bufferSize;?}
????????????
set?{?bufferSize?=?value;?}
????????}

????????
//?容納接收數據的緩存
????????private?byte[]?buffer;

????????
public?AsyncProcessor(string?fileName)
????????{
????????????buffer?
=?new?byte[bufferSize];

????????????
//?打開文件,指定參數為true以提供對異步操作的支持
????????????inputStream?=?new?FileStream(fileName,?FileMode.Open,?FileAccess.Read,?FileShare.Read,?bufferSize,?true);
????????}

????????
public?void?StartProcess()
????????{
????????????
//?開始異步讀取文件,填充緩存區
????????????inputStream.BeginRead(buffer,?0,?buffer.Length,?OnCompletedRead,?null);
????????}

????????
private?void?OnCompletedRead(IAsyncResult?asyncResult)
????????{
????????????
//?已經異步讀取一個?塊?,接收數據
????????????int?bytesRead?=?inputStream.EndRead(asyncResult);

????????????
//?如果沒有讀取任何字節,則流已達文件結尾
????????????if?(bytesRead?>?0)
????????????{
????????????????
//?暫停以模擬對數據塊的處理
????????????????Debug.WriteLine("??異步線程:已讀取一塊");
????????????????Thread.Sleep(TimeSpan.FromMilliseconds(
20));

????????????????
//?開始讀取下一塊
????????????????inputStream.BeginRead(buffer,?0,?buffer.Length,?OnCompletedRead,?null);
????????????}
????????????
else
????????????{
????????????????
//?結束操作
????????????????Debug.WriteLine("??異步線程:讀取文件結束");
????????????????inputStream.Close();
????????????}
????????}
????}

? ? 使用該類時可以這么寫:
????//?開始在另一線程中異步讀取文件
????AsyncProcessor?asyncIO?=?new?AsyncProcessor("test.txt");
????asyncIO.StartProcess();

????
//?在主程序中,做其它事情,這里簡單地循環10秒
????DateTime?startTime?=?DateTime.Now;
????
while?(DateTime.Now.Subtract(startTime).TotalSeconds?<?10)
????{
????????Debug.WriteLine(
"主程序:正在進行");
????????
//?暫停線程以模擬耗時的操作
????????Thread.Sleep(TimeSpan.FromMilliseconds(100));
????}

????Debug.WriteLine(
"主程序:已完成");
? ??
??? 問題4:如何創建臨時文件
??? 解決方案:
??? 有時需要在特定用戶的臨時目錄下創建一個臨時文件,這要求該文件具有唯一的名稱,避免與其它程序生成的臨時文件相沖突。我們會有多種選擇。最簡單的是,在程序所在目錄內使用GUID或時間戳加上隨機值作為文件名稱。但Path類提供的方法還是可以為你節省工作量,這就是它的靜態GetTempFileName方法,它在當前用戶的臨時目錄下創建一個臨時文件(文件名稱一定是唯一的),臨時目錄通常類似于這樣:C:\Documents and Settings\[username]\Local Settings\Temp。
????string?tempFile?=?Path.GetTempFileName();

????
using?(FileStream?fs?=?new?FileStream(tempFile,?FileMode.Open))
????{
????????
using?(BinaryWriter?writer?=?new?BinaryWriter(fs))
????????{
????????????
//?寫入數據
????????????writer.Write("臨時文件信息");
????????}
????}

????
//?Do?something

????
//?最后刪除臨時文件
????File.Delete(tempFile);

??? 問題5:如何獲得隨機文件名
??? 解決方案:
??? 使用Path.GetRandomFileName方法,它與GetTempFileName方法的不同之處在于它僅僅返回一個字符串但不會創建文件。

??? 問題6:監視文件系統的變化
??? 解決方案:
??? 如果指定路徑內的文件發生改變(比如文件被修改或創建),你希望能對此作出反應。
??? 如果程序與其它多個程序或業務處理相關,常常需要創建一個程序,并且只有文件系統發生變化時它才處于活動狀態。你可以創建一個這樣的程序,讓它定期區檢測指定目錄,此時會發現有件事情讓你苦惱:檢測得越頻繁,就會浪費越多的系統資源;而檢測得越少,那么檢測到變化的時間就會越長。
??? 這時可以使用FileSystemWatcher組件,指定要進行監視的目錄或文件,并處理其Created,Deleted,Renamed,Changed事件。
??? 要使用FileSystemWatcher組件,首先要創建它的一個實例,然后設置下列屬性:
??? Path:指定要監視的目錄;
??? Filter:指定要監視的文件類型,如”*.txt”;
??? NotifyFilter:指定要監視的變化類型;
??? FileSystemWatcher會引發四個關鍵的事件:Created,Deleted,Renamed,Changed。這些事件都在其FileSystemEventArgs參數中提供了相關文件的信息:如文件名,路徑,改變類型,Renamed事件中還可以了解到改變前的文件名和路徑。如果要禁用這些事件,則將它的EnableRaisingEvents屬性設置為false。Created,Deleted,Renamed三個事件比較容易處理,但Changed事件就得當心了,你需要設置它的NotifyFilter屬性以指示監視那些類型的變化。否則,程序會在文件被修改時淹沒在不斷發生的事件中(緩存區溢出)。

????//?設置相關屬性
????watcher.Path?=?appPath;
????watcher.Filter?
=?"*.txt";
????watcher.IncludeSubdirectories?
=?true;

????
//?添加事件處理函數
????watcher.Created?+=?new?FileSystemEventHandler(OnChanged);
????watcher.Deleted?
+=?new?FileSystemEventHandler(OnChanged);
????watcher.Changed?
+=?new?FileSystemEventHandler(OnChanged);
????watcher.Renamed?
+=?new?RenamedEventHandler(OnRenamed);

????void?OnRenamed(object?sender,?RenamedEventArgs?e)
????{
????????
string?renamedFormat?=?"File:?{0}?被重命名為?:{1}";
????????txtChangedInfo.Text?
=?string.Format(renamedFormat,?e.OldFullPath,?e.FullPath);
????}

????
void?OnChanged(object?sender,?FileSystemEventArgs?e)
????{
????????
//?顯示通知信息
????????txtChangedInfo.Text?=?"文件:?"?+?e.FullPath?+?"發生改變"?+?Environment.NewLine;
????????txtChangedInfo.Text?
+=?"改變類型:?"?+?e.ChangeType.ToString();
????}

??? 問題7:如何使用獨立存儲文件
??? 解決方案:
??? 有時你需要將數據存儲在文件中,但對本地硬盤驅動器卻沒有必要的權限(FileIOPermission)。這時要用到System.IO.IsolatedStorage命名空間中的類,這些類允許你的程序在特定用戶的目錄下將數據寫入文件而不需要直接訪問硬盤驅動器的權限:

????//?創建當前用戶的獨立存儲
????using?(IsolatedStorageFile?store?=?IsolatedStorageFile.GetUserStoreForAssembly())
????{
????????
//?創建一個文件夾
????????store.CreateDirectory("MyFolder");

????????
//?創建一個獨立存儲文件
????????using?(Stream?fs?=?new?IsolatedStorageFileStream("MyFile.txt",?FileMode.Create,?store))
????????{
????????????StreamWriter?writer?
=?new?StreamWriter(fs);
????????????writer.WriteLine(
"Test?Line!");
????????????writer.Flush();
????????}

????????Debug.WriteLine(
"當前大小:"?+?store.CurrentSize.ToString()?+?Environment.NewLine);
????????Debug.WriteLine(
"范圍:"?+?store.Scope.ToString()?+?Environment.NewLine);
????????
string[]?files?=?store.GetFileNames("*.*");
????????
if?(files.Length?>?0)
????????{
????????????Debug.WriteLine(
"當前文件:"?+?Environment.NewLine);
????????????
foreach?(string?file?in?files)
????????????{
????????????????Debug.WriteLine(file?
+?Environment.NewLine);
????????????}
????????}
??? }

??? 注意:本文部分內容為作示例都作了簡化,所以肯定會有不合理之處,僅希望能為您提供一些線索和思路。在使用前還請多多參考相關資料。