一、如何理解FIleStream
通過前3章的學(xué)些,相信大家對于Stream已經(jīng)有一定的了解,但是又如何去理解FileStream呢?請看下圖:
我們磁盤中的任何文件都是通過二進(jìn)制數(shù)組組成,最為直觀的就是記事本了,當(dāng)我們新建一個(gè)記事本時(shí),它的大小時(shí)0KB,我們每次輸入一個(gè)數(shù)字或字母時(shí),文件便會自動(dòng)增大到4KB,可見,隨著我們輸入的內(nèi)容越來越多,文件也會越來越大,同理,當(dāng)我們刪除文件內(nèi)容時(shí),文件也會相應(yīng)的減小,對了,聰明的你肯定會問:誰將內(nèi)容以怎樣的形式放到文件中去了?
好問題,還記得第一章流的概念嘛?對了,真實(shí)世界的一群魚可以通過河流往前往各個(gè)地方,F(xiàn)ileStream也一樣,byte可以通過FileStream進(jìn)行傳輸,這樣我們便能在計(jì)算機(jī)上對任何文件進(jìn)行一系列操作了。
二、FileStream的重要性
FileStream顧名思義文件流,我們電腦上的文件都可以通過文件流進(jìn)行操作,例如文件的復(fù)制、簡介、粘貼、刪除、本地文件上傳、下載、等許多重要的功能都離不開文件流。所以文件流不僅在本機(jī)上非常重要,在如今的網(wǎng)絡(luò)世界上也萬萬不能缺少的,想象一下我們開啟虛擬機(jī)后,直接從本地復(fù)制一個(gè)文件到虛擬機(jī)上,時(shí)多么的方便,如果沒有文件流,這個(gè)將難以想象。(大家別誤解,文件流通過網(wǎng)絡(luò)流將客戶都安上傳的文件傳到服務(wù)器端接收,然后通過文件流進(jìn)行處理,下載正好相反)
三、FileStream常用構(gòu)造函數(shù)介紹
1、FileStream(SafeFileHandle safeFileHandle,FileAccess fileAccess)
非托管參數(shù)SafeFileHandle簡單介紹
SafeFileHandle:是一個(gè)文件安全句柄,這樣的解釋可能大家一頭霧水,別急,大家先不要去理睬這深邃的含義,只要知道這個(gè)類型是C#非托管資源,也就是說它能夠調(diào)用非托管資源的方法,而且不屬于C#回收機(jī)制,所以我們必須使用GC手動(dòng)或其他方式(Finalize或Dispose方法)進(jìn)行非托管資源的回收,所以SafeFileHandle是一個(gè)默默無聞的保鏢,一直暗中保護(hù)FileStream和文件的安全,為了讓大家更好的理解這個(gè)保鏢,請看第一段代碼:
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 }
我們運(yùn)行一下,結(jié)果報(bào)錯(cuò)了,我看看一下錯(cuò)誤:
為什么會報(bào)錯(cuò)呢?其實(shí)程序被卡在了Console.ReadLine()這里,F(xiàn)ileStream并沒有被釋放,系統(tǒng)不知道這個(gè)文件是否還有用,所以幫我們保護(hù)這個(gè)文件(那個(gè)非托管資源SafeFileHandle所使用的內(nèi)存還被占用著)所以SafeFileHandle在內(nèi)部保護(hù)了這個(gè)文件從而報(bào)出了這個(gè)異常,如果我們將流關(guān)閉后,這個(gè)問題就不存在了。
所以,我們又回到了一個(gè)老問題上面,我們每次使用完FileStream后都必須將他關(guān)閉并釋放資源。
2、FileStream(string str,FileModel model)
string 參數(shù)表示文件所在的地址,F(xiàn)ileMode是個(gè)枚舉,表示確定如何打開或創(chuàng)建文件 。
FileModel枚舉參數(shù)包含以下內(nèi)容:
成員名稱
說明
Append
打開現(xiàn)有文件并查找到文件尾,或創(chuàng)建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。
Create
指定操作系統(tǒng)應(yīng)創(chuàng)建新文件。如果文件已存在,它將被改寫。這要求 FileIOPermissionAccess.Write。
System.IO.FileMode.Create 等效于這樣的請求:如果文件不存在,則使用 CreateNew;否則使用 Truncate。
CreateNew
指定操作系統(tǒng)應(yīng)創(chuàng)建新文件。此操作需要 FileIOPermissionAccess.Write。如果文件已存在,則將引發(fā) IOException。
Open
指定操作系統(tǒng)應(yīng)打開現(xiàn)有文件。打開文件的能力取決于 FileAccess 所指定的值。如果該文件不存在,
則引發(fā) System.IO.FileNotFoundException。
OpenOrCreate
指定操作系統(tǒng)應(yīng)打開文件(如果文件存在);否則,應(yīng)創(chuàng)建新文件。如果用 FileAccess.Read 打開文件,則需要
FileIOPermissionAccess.Read。如果文件訪問為 FileAccess.Write 或 FileAccess.ReadWrite,則需要
FileIOPermissionAccess.Write。如果文件訪問為 FileAccess.Append,則需要 FileIOPermissionAccess.Append。
Truncate
指定操作系統(tǒng)應(yīng)打開現(xiàn)有文件。文件一旦打開,就將被截?cái)酁榱阕止?jié)大小。此操作需要 FileIOPermissionAccess.Write。
試圖從使用 Truncate 打開的文件中進(jìn)行讀取將導(dǎo)致異常。
3、FileStream(IntPtr intPtr,FIleAccess fileAccess,Boolean ownsHandle)
FileAccess參數(shù)也是一個(gè)枚舉,表示對該文件的操作權(quán)限:
參數(shù)ownsHandle:也就是類似于前面和大家介紹的SafeFileHandler,有2點(diǎn)必須注意:(1)對于指定的文件句柄,操作系統(tǒng)不允許所請求的access,例如:當(dāng)access為Write或ReadWrite而文件句柄設(shè)置為只讀訪問的時(shí)候,會出現(xiàn)異常。所以ownsHandle才是老大,F(xiàn)ileAccess的權(quán)限應(yīng)該在ownsHandle的范圍內(nèi)。(2)FileStream假定它的句柄有獨(dú)占控制權(quán),當(dāng)FileStream也持有句柄時(shí),讀取、寫入或查找可能會導(dǎo)致數(shù)據(jù)破壞,為了數(shù)據(jù)安全,請使用句柄前調(diào)用Flush,并避免在使用完句柄后調(diào)用Close以外的任何方法。
4、FileStream(string str,FileModel model,FileAccess,fileAccess,FileShare fileShare)
FileShare:同樣時(shí)一個(gè)枚舉類型,確定文件如何由進(jìn)程共享。
Delete
允許隨后刪除文件。
Inheritable
使文件句柄可由子進(jìn)程繼承。Win32 不直接支持此功能。
None
謝絕共享當(dāng)前文件。文件關(guān)閉前,打開該文件的任何請求(由此進(jìn)程或另一進(jìn)程發(fā)出的請求)都將失敗。
Read
允許隨后打開文件讀取。如果未指定此標(biāo)志,則文件關(guān)閉前,任何打開該文件以進(jìn)行讀取的請求(由此進(jìn)程或另一進(jìn)程發(fā)出的請求)都將失敗。但是,即使指定了此標(biāo)志,仍可能需要附加權(quán)限才能夠訪問該文件。
ReadWrite
允許隨后打開文件讀取或?qū)懭?。如果未指定此?biāo)志,則文件關(guān)閉前,任何打開該文件以進(jìn)行讀取或?qū)懭氲恼埱螅ㄓ纱诉M(jìn)程或另一進(jìn)程發(fā)出)都將失敗。但是,即使指定了此標(biāo)志,仍可能需要附加權(quán)限才能夠訪問該文件。
Write
允許隨后打開文件寫入。如果未指定此標(biāo)志,則文件關(guān)閉前,任何打開該文件以進(jìn)行寫入的請求(由此進(jìn)程或另一進(jìn)過程發(fā)出的請求)都將失敗。但是,即使指定了此標(biāo)志,仍可能需要附加權(quán)限才能夠訪問該文件。
5、FileStream(string str,FileMode mode,FileAccess fileAccess,FileShare fileShare,Int32 i,Boolean async)
Int32:這是一個(gè)緩沖區(qū)的大小,大家可以按照自己的需要定制;
Boolean async:是否異步讀寫,告訴FileStream示例,是否采用異步讀寫
6、FileStream(string str,FileMode mode,FileShare fileShare,Int32 i,FileOption fileOption)
FileOption:這是類似于FileStream對于我呢見操作的高級選項(xiàng)
四、FileStream常用屬性介紹
1、CanRead:指示FileStream是否可以讀操作
2、CanSeek:指示FileStream是否可以跟蹤查找流操作
3、IsAsync:FileStream是否同步工作還是異步工作
4、Name:FileStream的名字,只讀屬性
5、ReadTimeout:設(shè)置讀取超時(shí)時(shí)間
6、SafeFileHandle:文件安全句柄,只讀屬性
7、Position:當(dāng)前FileStream所在的流的位置
五、FileStream常用方法介紹
以下方法重寫了Stream的一些虛方法
1、IAsyncResult BeginRead 異步讀取
2、IAsyncResult BeginWrite 異步寫
3、void Close 關(guān)閉當(dāng)前FileStream
4、void EndRead 異步讀取結(jié)束
5、void EndWrite 異步寫結(jié)束
6、void Flush 立刻釋放緩沖區(qū),將數(shù)據(jù)全部導(dǎo)出到基礎(chǔ)流(文件)中
7、int Read 一般讀取
8、int ReadByte 讀取單個(gè)字節(jié)
9、long Seek 跟蹤查找流所在的位置
10、void SetLength 設(shè)置FileStream的長度
11、void Write 一般寫
12、void WriteByte 寫入單個(gè)字節(jié)
六、屬于FileStream獨(dú)有的方法
1、FileSecurity GetAccessControl()
這個(gè)不是很常用,F(xiàn)ileSecurity時(shí)文件安全類,直接表達(dá)當(dāng)前文件的訪問控制列表(ACL)的復(fù)合當(dāng)前文件權(quán)限的項(xiàng)目,ACL大家有個(gè)了解就行,以后會單獨(dú)和大家討論下ACL方面的知識
2、void Lock(long position,long length)
這個(gè)Lock方法和線程中的Lock關(guān)鍵字很不一樣,它能夠鎖住文件中的某一部分,非常的強(qiáng)悍!用了這個(gè)方法我們能夠精確鎖定住我們要鎖住的文件的部分內(nèi)容
3、void SetAccessControl(FileSecurity fileSecurity)
和GetAccessControl很相似,ACL技術(shù)會再以后單獨(dú)介紹
4、void Unlock(long position,long length)
正好和lock方法相反,對于文件部分的解鎖
七、文件的新建和拷貝(主要演示文件同步和異步操作)
首先我們嘗試DIY一個(gè)IFileCOnfig
1 public interface IFileConfig2 {3 string FileName { get; set; }4 bool IsAsync { get; set; }5 }
創(chuàng)建文件配置類CreateFileConfig,用于添加文件一些配置設(shè)置,實(shí)現(xiàn)添加文件的操作
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 /// 創(chuàng)建文件所在Url13 /// </summary>14 public string CreateUrl { get; set; }15 }
讓我們定義一個(gè)文件流測試類:FileStreamTest來實(shí)現(xiàn)文件的操作。
1 /// <summary>2 /// 文件測試類3 /// </summary>4 public class FileStreamTest
在該類中實(shí)現(xiàn)一個(gè)簡單的Create方法,用來同步或異步的實(shí)現(xiàn)添加文件,F(xiàn)ileStream會根據(jù)配置類去選擇相應(yīng)的構(gòu)造函數(shù),實(shí)現(xiàn)異步或同步的添加方式
1 /// <summary> 2 /// 添加文件方法 3 /// </summary> 4 /// <param name="config"></param> 5 public void Create(IFileConfig config) 6 { 7 lock (_lockObject) 8 { 9 //得到創(chuàng)建文件配置的對象10 var createFileConfig = config as CreateFileConfig;11 //假設(shè)創(chuàng)建完文件后寫入一段話,實(shí)際項(xiàng)目中無需這么做,這里只是演示12 char[] insertContent = "HellowWord".ToCharArray();13 if (createFileConfig == null)14 {15 return;16 }17 //轉(zhuǎn)化成byte[]18 byte[] byteArrayContent = Encoding.Default.GetBytes(insertContent, 0, insertContent.Length);19 //根據(jù)傳入的配置文件來決定是否同步或者異步實(shí)例化Stream對象20 FileStream stream = createFileConfig.IsAsync21 ? 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只支持網(wǎng)絡(luò)流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 }
如果采用異步的方式則最后會進(jìn)入End_CreateFileCallBack回調(diào)方法,result AsyncState 對象就是上圖stream.BeginWrite()方法的最后一個(gè)參數(shù)。還有一點(diǎn)必須注意的是每一次使用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 //結(jié)束異步寫10 if (stream != null)11 {12 Console.WriteLine("異步創(chuàng)建文件地址{0}", stream.Name);13 stream.EndWrite(result);14 }15 16 Console.ReadKey();17 }
文件復(fù)制的方式思路比較相似,首先定義復(fù)制文件配置類,由于在異步回調(diào)中用到該配置類的屬性,所以新增了文件流對象和相應(yīng)的字節(jié)數(shù)組
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 //結(jié)束異步讀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("異步復(fù)制原文件地址:{0}", config.OriginalFileStream.Name);18 Console.WriteLine("復(fù)制后的新文件地址:{0}", config.DestinationFileUrl);19 //調(diào)用異步寫方法callBack方法為End_CreateFileCallBack,參數(shù)是copyStream20 copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length,21 End_CreateFileCallBack, copyStream);22 }23 }24 }
然后在FileStreamTest類中新增一個(gè)Copy方法實(shí)現(xiàn)文件的復(fù)制功能
1 /// <summary> 2 /// 復(fù)制文件 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 //創(chuàng)建同步或異步流16 FileStream stream = copyFileConfig.IsAsync17 ? new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read,18 4096, true)19 : new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open);20 //定義一個(gè)byte數(shù)組接收從原文件讀取的byte數(shù)據(jù)21 byte[] originalFileBytes = new byte[stream.Length];22 using (stream)23 {24 //如果異步流25 if (stream.IsAsync)26 {27 //將該流和流獨(dú)處的byte[]數(shù)據(jù)放入配置類,在callback中可以使用28 copyFileConfig.OriginalFileStream = stream;29 copyFileConfig.OriginalFileBytes = originalFileBytes;30 if (stream.CanRead)31 {32 //異步開始讀取,讀取完后進(jìn)入End_ReadFileCallBack方法,該方法接收copyFileConfig參數(shù)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 //定義一個(gè)寫流,在新位置中創(chuàng)建一個(gè)文件44 FileStream copyStream = new FileStream(copyFileConfig.DestinationFileUrl, FileMode.CreateNew);45 using (copyStream)46 {47 //將原文件的內(nèi)容寫進(jìn)新文件48 copyStream.Write(originalFileBytes, 0, originalFileBytes.Length);49 }50 }51 52 Console.ReadLine();53 }54 }55 }56 }
最后,如果采用異步的方式,則會進(jìn)入End_ReadFileCallBack回調(diào)函數(shù)進(jìn)行異步讀取和異步寫操作
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 //結(jié)束異步讀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("異步復(fù)制原文件地址:{0}", config.OriginalFileStream.Name);18 Console.WriteLine("復(fù)制后的新文件地址:{0}", config.DestinationFileUrl);19 //調(diào)用異步寫方法callBack方法為End_CreateFileCallBack,參數(shù)是copyStream20 copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length,21 End_CreateFileCallBack, copyStream);22 }23 }24 }
最有讓我們在Main函數(shù)調(diào)用一下:
1 static void Main(string[] args) 2 { 3 //文件操作測試 4 FileStreamTest test = new FileStreamTest(); 5 //創(chuàng)建文件配置類 6 CreateFileConfig createFileConfig = new CreateFileConfig 7 { 8 CreateUrl = @"E:\自己的\MyTest\Word\新建的.txt", 9 IsAsync = true10 };11 //復(fù)制文件配置類12 CopyFileConfig copyFileConfig = new CopyFileConfig13 {14 OriginalFileUrl = @"E:\自己的\MyTest\Word\TextFile1.txt",15 DestinationFileUrl = @"E:\自己的\MyTest\Word\TextFile1-副本.txt",16 IsAsync = true17 };18 //test.Create(createFileConfig);19 test.Copy(copyFileConfig);20 Console.ReadKey();21 }
輸出結(jié)果:
好了,F(xiàn)ileStream的相關(guān)知識就分享到這里了。