【Stream—4】FileStream相關知識分享


一、如何理解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的相關知識就分享到這里了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM