摘要:
檔案操作是程式中非常基礎和重要的内容,而路徑、檔案、目錄以及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 (BinaryWriter writer = new BinaryWriter(fs))
// 寫入十進制數,字元串和字元
writer.Write(234.56M);
writer.Write("String");
writer.Write('!');
讀取檔案的示例:
// 以隻讀模式打開一個二進制檔案
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))
// 寫入資料
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);
}
注意:本文部分内容為作示例都作了簡化,是以肯定會有不合理之處,僅希望能為您提供一些線索和思路。在使用前還請多多參考相關資料。
Anders Cui
參考:
Apress-Visual C# 2005 Recipes A Problem Solution Approach
MSDN
本文轉自一個程式員的自省部落格園部落格,原文連結:http://www.cnblogs.com/anderslly/archive/2007/01/03/readwritefile.html,如需轉載請自行聯系原作者。