一、如何理解FIleStream
通過前3章的學些,相信大家對于Stream已經有一定的了解,但是又如何去理解FileStream呢?請看下圖:

我們磁盤中的任何檔案都是通過二進制陣列組成,最為直觀的就是記事本了,當我們新建一個記事本時,它的大小時0KB,我們每次輸入一個數字或字母時,檔案便會自動增大到4KB,可見,隨著我們輸入的內容越來越多,檔案也會越來越大,同理,當我們洗掉檔案內容時,檔案也會相應的減小,對了,聰明的你肯定會問:誰將內容以怎樣的形式放到檔案中去了?
好問題,還記得第一章流的概念嘛?對了,真實世界的一群魚可以通過河流往前往各個地方,FileStream也一樣,byte可以通過FileStream進行傳輸,這樣我們便能在計算機上對任何檔案進行一系列操作了,
二、FileStream的重要性
FileStream顧名思義檔案流,我們電腦上的檔案都可以通過檔案流進行操作,例如檔案的復制、簡介、粘貼、洗掉、本地檔案上傳、下載、等許多重要的功能都離不開檔案流,所以檔案流不僅在本機上非常重要,在如今的網路世界上也萬萬不能缺少的,想象一下我們開啟虛擬機后,直接從本地復制一個檔案到虛擬機上,時多么的方便,如果沒有檔案流,這個將難以想象,(大家別誤解,檔案流通過網路流將客戶都安上傳的檔案傳到服務器端接收,然后通過檔案流進行處理,下載正好相反)
三、FileStream常用建構式介紹
1、FileStream(SafeFileHandle safeFileHandle,FileAccess fileAccess)
非托管引數SafeFileHandle簡單介紹
SafeFileHandle:是一個檔案安全句柄,這樣的解釋可能大家一頭霧水,別急,大家先不要去理睬這深邃的含義,只要知道這個型別是C#非托管資源,也就是說它能夠呼叫非托管資源的方法,而且不屬于C#回識訓制,所以我們必須使用GC手動或其他方式(Finalize或Dispose方法)進行非托管資源的回收,所以SafeFileHandle是一個默默無聞的保鏢,一直暗中保護FileStream和檔案的安全,為了讓大家更好的理解這個保鏢,請看第一段代碼:
1 static void Main(string[] args) 2 { 3 var rootPath = Environment.CurrentDirectory; 4 var fileName = Path.Combine(rootPath, "TextFile1.txt");//@"TextFile1.txt"; 5 FileStream fileStream = new FileStream(fileName, FileMode.OpenOrCreate); 6 Console.ReadLine(); 7 File.Delete(fileName); 8 Console.ReadKey(); 9 }
我們運行一下,結果報錯了,我看看一下錯誤:

為什么會報錯呢?其實程式被卡在了Console.ReadLine()這里,FileStream并沒有被釋放,系統不知道這個檔案是否還有用,所以幫我們保護這個檔案(那個非托管資源SafeFileHandle所使用的記憶體還被占用著)所以SafeFileHandle在內部保護了這個檔案從而報出了這個例外,如果我們將流關閉后,這個問題就不存在了,
所以,我們又回到了一個老問題上面,我們每次使用完FileStream后都必須將他關閉并釋放資源,
2、FileStream(string str,FileModel model)
string 引數表示檔案所在的地址,FileMode是個列舉,表示確定如何打開或創建檔案 ,
FileModel列舉引數包含以下內容:
|
成員名稱 |
說明 |
|
Append |
打開現有檔案并查找到檔案尾,或創建新檔案,FileMode.Append 只能同 FileAccess.Write 一起使用, |
|
Create |
指定作業系統應創建新檔案,如果檔案已存在,它將被改寫,這要求 FileIOPermissionAccess.Write, System.IO.FileMode.Create 等效于這樣的請求:如果檔案不存在,則使用 CreateNew;否則使用 Truncate, |
|
CreateNew |
指定作業系統應創建新檔案,此操作需要 FileIOPermissionAccess.Write,如果檔案已存在,則將引發 IOException, |
|
Open |
指定作業系統應打開現有檔案,打開檔案的能力取決于 FileAccess 所指定的值,如果該檔案不存在, 則引發 System.IO.FileNotFoundException, |
|
OpenOrCreate |
指定作業系統應打開檔案(如果檔案存在);否則,應創建新檔案,如果用 FileAccess.Read 打開檔案,則需要 FileIOPermissionAccess.Read,如果檔案訪問為 FileAccess.Write 或 FileAccess.ReadWrite,則需要 FileIOPermissionAccess.Write,如果檔案訪問為 FileAccess.Append,則需要 FileIOPermissionAccess.Append, |
|
Truncate |
指定作業系統應打開現有檔案,檔案一旦打開,就將被截斷為零位元組大小,此操作需要 FileIOPermissionAccess.Write, 試圖從使用 Truncate 打開的檔案中進行讀取將導致例外, |
3、FileStream(IntPtr intPtr,FIleAccess fileAccess,Boolean ownsHandle)
FileAccess引數也是一個列舉,表示對該檔案的操作權限:

引數ownsHandle:也就是類似于前面和大家介紹的SafeFileHandler,有2點必須注意:(1)對于指定的檔案句柄,作業系統不允許所請求的access,例如:當access為Write或ReadWrite而檔案句柄設定為只讀訪問的時候,會出現例外,所以ownsHandle才是老大,FileAccess的權限應該在ownsHandle的范圍內,(2)FileStream假定它的句柄有獨占控制權,當FileStream也持有句柄時,讀取、寫入或查找可能會導致資料破壞,為了資料安全,請使用句柄前呼叫Flush,并避免在使用完句柄后呼叫Close以外的任何方法,
4、FileStream(string str,FileModel model,FileAccess,fileAccess,FileShare fileShare)
FileShare:同樣時一個列舉型別,確定檔案如何由行程共享,
|
Delete |
允許隨后洗掉檔案, |
|
Inheritable |
使檔案句柄可由子行程繼承,Win32 不直接支持此功能, |
|
None |
謝絕共享當前檔案,檔案關閉前,打開該檔案的任何請求(由此行程或另一行程發出的請求)都將失敗, |
|
Read |
允許隨后打開檔案讀取,如果未指定此標志,則檔案關閉前,任何打開該檔案以進行讀取的請求(由此行程或另一行程發出的請求)都將失敗,但是,即使指定了此標志,仍可能需要附加權限才能夠訪問該檔案, |
|
ReadWrite |
允許隨后打開檔案讀取或寫入,如果未指定此標志,則檔案關閉前,任何打開該檔案以進行讀取或寫入的請求(由此行程或另一行程發出)都將失敗,但是,即使指定了此標志,仍可能需要附加權限才能夠訪問該檔案, |
|
Write |
允許隨后打開檔案寫入,如果未指定此標志,則檔案關閉前,任何打開該檔案以進行寫入的請求(由此行程或另一程序序發出的請求)都將失敗,但是,即使指定了此標志,仍可能需要附加權限才能夠訪問該檔案, |
5、FileStream(string str,FileMode mode,FileAccess fileAccess,FileShare fileShare,Int32 i,Boolean async)
Int32:這是一個緩沖區的大小,大家可以按照自己的需要定制;
Boolean async:是否異步讀寫,告訴FileStream示例,是否采用異步讀寫
6、FileStream(string str,FileMode mode,FileShare fileShare,Int32 i,FileOption fileOption)
FileOption:這是類似于FileStream對于我呢見操作的高級選項
四、FileStream常用屬性介紹
1、CanRead:指示FileStream是否可以讀操作
2、CanSeek:指示FileStream是否可以跟蹤查找流操作
3、IsAsync:FileStream是否同步作業還是異步作業
4、Name:FileStream的名字,只讀屬性
5、ReadTimeout:設定讀取超時時間
6、SafeFileHandle:檔案安全句柄,只讀屬性
7、Position:當前FileStream所在的流的位置
五、FileStream常用方法介紹
以下方法重寫了Stream的一些虛方法
1、IAsyncResult BeginRead 異步讀取
2、IAsyncResult BeginWrite 異步寫
3、void Close 關閉當前FileStream
4、void EndRead 異步讀取結束
5、void EndWrite 異步寫結束
6、void Flush 立刻釋放緩沖區,將資料全部匯出到基礎流(檔案)中
7、int Read 一般讀取
8、int ReadByte 讀取單個位元組
9、long Seek 跟蹤查找流所在的位置
10、void SetLength 設定FileStream的長度
11、void Write 一般寫
12、void WriteByte 寫入單個位元組
六、屬于FileStream獨有的方法
1、FileSecurity GetAccessControl()
這個不是很常用,FileSecurity時檔案安全類,直接表達當前檔案的訪問控制串列(ACL)的復合當前檔案權限的專案,ACL大家有個了解就行,以后會單獨和大家討論下ACL方面的知識
2、void Lock(long position,long length)
這個Lock方法和執行緒中的Lock關鍵字很不一樣,它能夠鎖住檔案中的某一部分,非常的強悍!用了這個方法我們能夠精確鎖定住我們要鎖住的檔案的部分內容
3、void SetAccessControl(FileSecurity fileSecurity)
和GetAccessControl很相似,ACL技識訓再以后單獨介紹
4、void Unlock(long position,long length)
正好和lock方法相反,對于檔案部分的解鎖
七、檔案的新建和拷貝(主要演示檔案同步和異步操作)
首先我們嘗試DIY一個IFileCOnfig
1 public interface IFileConfig 2 { 3 string FileName { get; set; } 4 bool IsAsync { get; set; } 5 }
創建檔案配置類CreateFileConfig,用于添加檔案一些配置設定,實作添加檔案的操作
1 public class CreateFileConfig : IFileConfig 2 { 3 /// <summary> 4 /// 檔案名稱 5 /// </summary> 6 public string FileName { get; set; } 7 /// <summary> 8 /// 是否異步 9 /// </summary> 10 public bool IsAsync { get; set; } 11 /// <summary> 12 /// 創建檔案所在Url 13 /// </summary> 14 public string CreateUrl { get; set; } 15 }
讓我們定義一個檔案流測驗類:FileStreamTest來實作檔案的操作,
1 /// <summary> 2 /// 檔案測驗類 3 /// </summary> 4 public class FileStreamTest
在該類中實作一個簡單的Create方法,用來同步或異步的實作添加檔案,FileStream會根據配置類去選擇相應的建構式,實作異步或同步的添加方式
1 /// <summary> 2 /// 添加檔案方法 3 /// </summary> 4 /// <param name="config"></param> 5 public void Create(IFileConfig config) 6 { 7 lock (_lockObject) 8 { 9 //得到創建檔案配置的物件 10 var createFileConfig = config as CreateFileConfig; 11 //假設創建完檔案后寫入一段話,實際專案中無需這么做,這里只是演示 12 char[] insertContent = "HellowWord".ToCharArray(); 13 if (createFileConfig == null) 14 { 15 return; 16 } 17 //轉化成byte[] 18 byte[] byteArrayContent = Encoding.Default.GetBytes(insertContent, 0, insertContent.Length); 19 //根據傳入的組態檔來決定是否同步或者異步實體化Stream物件 20 FileStream stream = createFileConfig.IsAsync 21 ? new FileStream(createFileConfig.CreateUrl, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 22 4096, true) 23 : new FileStream(createFileConfig.CreateUrl, FileMode.Create); 24 using (stream) 25 { 26 //如果不注釋下面代碼會拋出例外,google上提示是WriteTimeOut只支持網路流 27 //stream.WriteTimeout=READ_OR_WRITE_TIMEOUT; 28 //如果流是同步并且可寫 29 if (!stream.IsAsync && stream.CanWrite) 30 { 31 stream.Write(byteArrayContent, 0, byteArrayContent.Length); 32 } 33 else if (stream.CanWrite)//異步可寫 34 { 35 stream.BeginWrite(byteArrayContent, 0, byteArrayContent.Length, End_CreateFileCallBack, stream); 36 } 37 } 38 } 39 }
如果采用異步的方式則最后會進入End_CreateFileCallBack回呼方法,result AsyncState 物件就是上圖stream.BeginWrite()方法的最后一個引數,還有一點必須注意的是每一次使用BeginWrite()方法都要帶上EndWrite()方法,Read方法也一樣
1 /// <summary> 2 /// 異步寫檔案callBack方法 3 /// </summary> 4 /// <param name="result"></param> 5 private void End_CreateFileCallBack(IAsyncResult result) 6 { 7 //從IAsyncResult物件中得到原來的FileStream 8 var stream = result.AsyncState as FileStream; 9 //結束異步寫 10 if (stream != null) 11 { 12 Console.WriteLine("異步創建檔案地址{0}", stream.Name); 13 stream.EndWrite(result); 14 } 15 16 Console.ReadKey(); 17 }
檔案復制的方式思路比較相似,首先定義復制檔案配置類,由于在異步回呼中用到該配置類的屬性,所以新增了檔案流物件和相應的位元組陣列
1 /// <summary> 2 /// 異步讀檔案方法 3 /// </summary> 4 /// <param name="result"></param> 5 private void End_ReadFileCallBack(IAsyncResult result) 6 { 7 //得到先前的組態檔 8 var config = result.AsyncState as CopyFileConfig; 9 //結束異步讀 10 config?.OriginalFileStream.EndRead(result); 11 //異步讀后立即寫入新檔案地址 12 if (config != null) 13 { 14 FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew, FileAccess.Write, FileShare.Write, 4096, true); 15 using (copyStream) 16 { 17 Console.WriteLine("異步復制原檔案地址:{0}", config.OriginalFileStream.Name); 18 Console.WriteLine("復制后的新檔案地址:{0}", config.DestinationFileUrl); 19 //呼叫異步寫方法callBack方法為End_CreateFileCallBack,引數是copyStream 20 copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length, 21 End_CreateFileCallBack, copyStream); 22 } 23 } 24 }
然后在FileStreamTest類中新增一個Copy方法實作檔案的復制功能
1 /// <summary> 2 /// 復制檔案 3 /// </summary> 4 /// <param name="config"></param> 5 public void Copy(IFileConfig config) 6 { 7 lock (_lockObject) 8 { 9 //得到CopyFileConfig物件 10 var copyFileConfig = config as CopyFileConfig; 11 if (copyFileConfig == null) 12 { 13 return; 14 } 15 //創建同步或異步流 16 FileStream stream = copyFileConfig.IsAsync 17 ? new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read, 18 4096, true) 19 : new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open); 20 //定義一個byte陣列接收從原檔案讀取的byte資料 21 byte[] originalFileBytes = new byte[stream.Length]; 22 using (stream) 23 { 24 //如果異步流 25 if (stream.IsAsync) 26 { 27 //將該流和流獨處的byte[]資料放入配置類,在callback中可以使用 28 copyFileConfig.OriginalFileStream = stream; 29 copyFileConfig.OriginalFileBytes = originalFileBytes; 30 if (stream.CanRead) 31 { 32 //異步開始讀取,讀取完后進入End_ReadFileCallBack方法,該方法接收copyFileConfig引數 33 stream.BeginRead(originalFileBytes, 0, originalFileBytes.Length, End_ReadFileCallBack, 34 copyFileConfig); 35 } 36 else//否則同步讀取 37 { 38 if (stream.CanRead) 39 { 40 //讀取原檔案 41 stream.Read(originalFileBytes, 0, originalFileBytes.Length); 42 } 43 //定義一個寫流,在新位置中創建一個檔案 44 FileStream copyStream = new FileStream(copyFileConfig.DestinationFileUrl, FileMode.CreateNew); 45 using (copyStream) 46 { 47 //將原檔案的內容寫進新檔案 48 copyStream.Write(originalFileBytes, 0, originalFileBytes.Length); 49 } 50 } 51 52 Console.ReadLine(); 53 } 54 } 55 } 56 }
最后,如果采用異步的方式,則會進入End_ReadFileCallBack回呼函式進行異步讀取和異步寫操作
1 /// <summary> 2 /// 異步讀檔案方法 3 /// </summary> 4 /// <param name="result"></param> 5 private void End_ReadFileCallBack(IAsyncResult result) 6 { 7 //得到先前的組態檔 8 var config = result.AsyncState as CopyFileConfig; 9 //結束異步讀 10 config?.OriginalFileStream.EndRead(result); 11 //異步讀后立即寫入新檔案地址 12 if (config != null) 13 { 14 FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew, FileAccess.Write, FileShare.Write, 4096, true); 15 using (copyStream) 16 { 17 Console.WriteLine("異步復制原檔案地址:{0}", config.OriginalFileStream.Name); 18 Console.WriteLine("復制后的新檔案地址:{0}", config.DestinationFileUrl); 19 //呼叫異步寫方法callBack方法為End_CreateFileCallBack,引數是copyStream 20 copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length, 21 End_CreateFileCallBack, copyStream); 22 } 23 } 24 }
最有讓我們在Main函式呼叫一下:
1 static void Main(string[] args) 2 { 3 //檔案操作測驗 4 FileStreamTest test = new FileStreamTest(); 5 //創建檔案配置類 6 CreateFileConfig createFileConfig = new CreateFileConfig 7 { 8 CreateUrl = @"E:\自己的\MyTest\Word\新建的.txt", 9 IsAsync = true 10 }; 11 //復制檔案配置類 12 CopyFileConfig copyFileConfig = new CopyFileConfig 13 { 14 OriginalFileUrl = @"E:\自己的\MyTest\Word\TextFile1.txt", 15 DestinationFileUrl = @"E:\自己的\MyTest\Word\TextFile1-副本.txt", 16 IsAsync = true 17 }; 18 //test.Create(createFileConfig); 19 test.Copy(copyFileConfig); 20 Console.ReadKey(); 21 }
輸出結果:

好了,FileStream的相關知識就分享到這里了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/103495.html
標籤:C#
上一篇:百度Sitemap生成器
下一篇:C# .NET的BinaryFormatter、protobuf-net、Newtonsoft.Json以及自己寫的序列化方法序列化效率和序列化后的檔案體積大小對比
