(一)
摘要:
文件操作是程序中非常基礎和重要的內容,而路徑、文件、目錄以及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方法,它會幫你處理煩人的”\”;
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;
}
(二)
摘要:
文件操作是程序中非常基礎和重要的內容,而路徑、文件、目錄以及I/O都是在進行文件操作時的常見主題,這裡想把這些常見的問題作個總結,對於每個問題,盡量提供一些解決方案,即使沒有你想要的答案,也希望能提供給你一點有益的思路,如果你有好的建議,懇請能夠留言,使這些內容更加完善。
主要內容:
一、路徑的相關操作, 如判斷路徑是否合法,路徑類型,路徑的特定部分,合並路徑,系統文件夾路徑等內容;
二、相關通用文件對話框,這些對話框可以幫助我們操作文件系統中的文件和目錄;
三、文件、目錄、驅動器的操作,如獲取它們的基本信息,獲取和設置文件和目錄的屬性,文件的版本信息,
搜索文件和目錄,文件判等,復制、移動、刪除、重命名文件和目錄;
四、讀寫文件,包括臨時文件,隨機文件名等;
五、對文件系統的監視;
上一篇介紹了第一、二部分,這一篇介紹一下最重要的第三部分。
三、文件和目錄相關操作
文件和目錄操作涉及的類主要是: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類型);
至此,我們已經了解了文件和目錄相關的一些基本操作。但還不清楚如何去讀寫文件的內容,下一篇中會詳細了解這方面的操作。
(三)
文件讀寫相關類介紹:
文件讀寫操作涉及的類主要是:
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。
問題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);
}
}
}
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);
dlgOpenFolder.Description = "選擇要進行計算的目錄";
dlgOpenFolder.RootFolder = Environment.SpecialFolder.MyComputer;
dlgOpenFolder.ShowNewFolderButton = true;
DialogResult result = dlgOpenFolder.ShowDialog(this);
if (result == DialogResult.OK)
...{
txtDirPath.Text = dlgOpenFolder.SelectedPath;
}
問題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屬性獲取用戶選定的路徑;