一、如何理解FIleStream 通過前3章的學些,相信大家對於Stream已經有一定的瞭解,但是又如何去理解FileStream呢?請看下圖: 我們磁碟中的任何文件都是通過二進位數組組成,最為直觀的就是記事本了,當我們新建一個記事本時,它的大小時0KB,我們每次輸入一個數字或字母時,文件便會自動增 ...
一、如何理解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的相關知識就分享到這裡了。