【转】C# 温故而知新:Stream篇(—)
? 目錄:
什么是Stream?
什么是字節(jié)序列?
Stream的構(gòu)造函數(shù)
Stream的重要屬性及方法
Stream的示例
Stream異步讀寫
Stream 和其子類的類圖
本章總結(jié)
?
?
?
什么是Stream?
MSDN 中的解釋太簡潔了:?提供字節(jié)序列的一般視圖
(我可不想這么理解,這必定讓我抓狂,我理解的流是向自然界的河流那樣清澈而又美麗,c#中的流也是一樣,許多技術(shù)或者說核心技術(shù)都需要流的幫忙)
那什么是字節(jié)序列呢?
其實(shí)簡單的來理解的話字節(jié)序列指的是:
字節(jié)對象都被存儲為連續(xù)的字節(jié)序列,字節(jié)按照一定的順序進(jìn)行排序組成了字節(jié)序列
那什么關(guān)于流的解釋可以抽象為下列情況:
打個比方:一條河中有一條魚游過,這個魚就是一個字節(jié),這個字節(jié)包括魚的眼睛,嘴巴,等組成8個二進(jìn)制,顯然這條河就是我們的核心對象:流
馬上進(jìn)入正題,讓我們來解釋下c#的 Stream?是如何使用的
讓我們直接溫故或?qū)W習(xí)下Stream類的結(jié)構(gòu),屬性和相關(guān)方法
首先是構(gòu)造函數(shù)
Stream 類有一個protected 類型的構(gòu)造函數(shù), 但是它是個抽象類,無法直接如下使用
Stream stream = new Stream();所以我們自定義一個流繼承自Stream 看看哪些屬性必須重寫或自定義:
public class MyStreamExample : Stream {public override bool CanRead{get { throw new NotImplementedException(); }}public override bool CanSeek{get { throw new NotImplementedException(); }}public override bool CanWrite{get { throw new NotImplementedException(); }}public override void Flush(){throw new NotImplementedException();}public override long Length{get { throw new NotImplementedException(); }}public override long Position{get{throw new NotImplementedException();}set{throw new NotImplementedException();}}public override int Read(byte[] buffer, int offset, int count){throw new NotImplementedException();}public override long Seek(long offset, SeekOrigin origin){throw new NotImplementedException();}public override void SetLength(long value){throw new NotImplementedException();}public override void Write(byte[] buffer, int offset, int count){throw new NotImplementedException();}}可以看出系統(tǒng)自動幫我們實(shí)現(xiàn)了Stream 的抽象屬性和屬性方法
?? 1:? CanRead: 只讀屬性,判斷該流是否能夠讀取:
?? 2:? CanSeek: 只讀屬性,判斷該流是否支持跟蹤查找
?? 3:? CanWrite: 只讀屬性,判斷當(dāng)前流是否可寫
*4: void Flush():這點(diǎn)必須說得仔細(xì)些:
????當(dāng)我們使用流寫文件時,數(shù)據(jù)流會先進(jìn)入到緩沖區(qū)中,而不會立刻寫入文件,當(dāng)執(zhí)行這個方法后,緩沖區(qū)的數(shù)據(jù)流會立即注入基礎(chǔ)流
???? MSDN中的描述:使用此方法將所有信息從基礎(chǔ)緩沖區(qū)移動到其目標(biāo)或清除緩沖區(qū),或者同時執(zhí)行這兩種操作。根據(jù)對象的狀態(tài),可能需要修
???? 改流內(nèi)的當(dāng)前位置(例如,在基礎(chǔ)流支持查找的情況下即如此)當(dāng)使用?StreamWriter?或?BinaryWriter?類時,不要刷新?Stream?基對象。
???? 而應(yīng)使用該類的?Flush?或?Close?方法,此方法確保首先將該數(shù)據(jù)刷新至基礎(chǔ)流,然后再將其寫入文件。
(紅色部分為關(guān)鍵請大家務(wù)必能夠理解,今后會在相應(yīng)的章節(jié)中介紹)
? 5: Length:表示流的長度
*6: Position屬性:(非常重要)
雖然從字面中可以看出這個Position屬性只是標(biāo)示了流中的一個位置而已,可是我們在實(shí)際開發(fā)中會發(fā)現(xiàn)這個想法會非常的幼稚,
很多asp.net項目中文件或圖片上傳中很多朋友會經(jīng)歷過這樣一個痛苦:Stream對象被緩存了,導(dǎo)致了Position屬性在流中無法
找到正確的位置,這點(diǎn)會讓人抓狂,其實(shí)解決這個問題很簡單,聰明的你肯定想到了,其實(shí)我們每次使用流前必須將Stream.Position
設(shè)置成0就行了,但是這還不能根本上解決問題,最好的方法就是用Using語句將流對象包裹起來,用完后關(guān)閉回收即可。
*7: abstract int Read(byte[] buffer, int offset, int count)
這個方法包含了3個關(guān)鍵的參數(shù):緩沖字節(jié)數(shù)組,位移偏量和讀取字節(jié)個數(shù),每次讀取一個字節(jié)后會返回一個緩沖區(qū)中的總字節(jié)數(shù)
第一個參數(shù):這個數(shù)組相當(dāng)于一個空盒子,Read()方法每次讀取流中的一個字節(jié)將其放進(jìn)這個空盒子中。(全部讀完后便可使用buffer字節(jié)數(shù)組了)
第二個參數(shù):表示位移偏量,指定數(shù)據(jù)從buffer的什么位置開始存放
最后一個參數(shù):就是讀取多少字節(jié)數(shù)。
返回值便是總共讀取了多少字節(jié)數(shù).
*8: abstract long Seek(long offset, SeekOrigin origin)
??? 大家還記得Position屬性么?其實(shí)Seek方法就是重新設(shè)定流中的一個位置,在說明offset參數(shù)作用之前大家先來了解下SeekOrigin這個枚舉:
如果 offset 為負(fù),則要求新位置位于 origin 指定的位置之前,其間隔相差 offset 指定的字節(jié)數(shù)。如果 offset 為零 (0),則要求新位置位于由 origin 指定的位置處。
如果 offset 為正,則要求新位置位于 origin 指定的位置之后,其間隔相差 offset 指定的字節(jié)數(shù).
Stream. Seek(-3,Origin.End);? 表示在流末端往前數(shù)第3個位置
Stream. Seek(0,Origin.Begin); 表示在流的開頭位置
Stream. Seek(3,Orig`in.Current); 表示在流的當(dāng)前位置往后數(shù)第三個位置
查找之后會返回一個流中的一個新位置。其實(shí)說道這大家就能理解Seek方法的精妙之處了吧
*9: abstract void Write(byte[] buffer,int offset,int count)
這個方法包含了3個關(guān)鍵的參數(shù):緩沖字節(jié)數(shù)組,位移偏量和讀取字節(jié)個數(shù)
和read方法不同的是 write方法中的第一個參數(shù)buffer已經(jīng)有了許多byte類型
的數(shù)據(jù),我們只需通過設(shè)置 offset和count來將buffer中的數(shù)據(jù)寫入流中
*10: virtual void Close()
關(guān)閉流并釋放資源,在實(shí)際操作中,如果不用using的話,別忘了使用完流之后將其關(guān)閉
這個方法特別重要,使用完當(dāng)前流千萬別忘記關(guān)閉!
?
為了讓大家能夠快速理解和消化上述屬性和方法我會寫個示例并且關(guān)鍵部分會詳細(xì)說明
static void Main(string[] args){byte[] buffer = null;string testString = "Stream!Hello world";char[] readCharArray = null;byte[] readBuffer = null;string readString = string.Empty;//關(guān)于MemoryStream 我會在后續(xù)章節(jié)詳細(xì)闡述using (MemoryStream stream = new MemoryStream()) {Console.WriteLine("初始字符串為:{0}", testString);//如果該流可寫if (stream.CanWrite){//首先我們嘗試將testString寫入流中//關(guān)于Encoding我會在另一篇文章中詳細(xì)說明,暫且通過它實(shí)現(xiàn)string->byte[]的轉(zhuǎn)換buffer = Encoding.Default.GetBytes(testString);//我們從該數(shù)組的第一個位置開始寫,長度為3,寫完之后 stream中便有了數(shù)據(jù)//對于新手來說很難理解的就是數(shù)據(jù)是什么時候?qū)懭氲搅髦?#xff0c;在冗長的項目代碼面前,我碰見過很//多新手都會有這種經(jīng)歷,我希望能夠用如此簡單的代碼讓新手或者老手們在溫故下基礎(chǔ)stream.Write(buffer, 0,3);Console.WriteLine("現(xiàn)在Stream.Postion在第{0}位置",stream.Position+1);//從剛才結(jié)束的位置(當(dāng)前位置)往后移3位,到第7位long newPositionInStream =stream.CanSeek? stream.Seek(3, SeekOrigin.Current):0;Console.WriteLine("重新定位后Stream.Postion在第{0}位置", newPositionInStream+1);if (newPositionInStream < buffer.Length){//將從新位置(第7位)一直寫到buffer的末尾,注意下stream已經(jīng)寫入了3個數(shù)據(jù)“Str”stream.Write(buffer, (int)newPositionInStream, buffer.Length - (int)newPositionInStream);}//寫完后將stream的Position屬性設(shè)置成0,開始讀流中的數(shù)據(jù)stream.Position = 0;// 設(shè)置一個空的盒子來接收流中的數(shù)據(jù),長度根據(jù)stream的長度來決定readBuffer = new byte[stream.Length];//設(shè)置stream總的讀取數(shù)量 ,//注意!這時候流已經(jīng)把數(shù)據(jù)讀到了readBuffer中int count = stream.CanRead?stream.Read(readBuffer, 0, readBuffer.Length):0;//由于剛開始時我們使用加密Encoding的方式,所以我們必須解密將readBuffer轉(zhuǎn)化成Char數(shù)組,這樣才能重新拼接成string//首先通過流讀出的readBuffer的數(shù)據(jù)求出從相應(yīng)Char的數(shù)量int charCount = Encoding.Default.GetCharCount(readBuffer, 0, count);//通過該Char的數(shù)量 設(shè)定一個新的readCharArray數(shù)組readCharArray = new char[charCount];//Encoding 類的強(qiáng)悍之處就是不僅包含加密的方法,甚至將解密者都能創(chuàng)建出來(GetDecoder()),//解密者便會將readCharArray填充(通過GetChars方法,把readBuffer 逐個轉(zhuǎn)化將byte轉(zhuǎn)化成char,并且按一致順序填充到readCharArray中)Encoding.Default.GetDecoder().GetChars(readBuffer, 0, count, readCharArray, 0);for (int i = 0; i < readCharArray.Length; i++){readString += readCharArray[i];}Console.WriteLine("讀取的字符串為:{0}", readString);}stream.Close();}Console.ReadLine();}
顯示結(jié)果:
大家需要特別注意的是stream.Positon這個很神奇的屬性,在復(fù)雜的程序中,往往流對象操作也會很復(fù)雜,
一定要切記將stream.Positon設(shè)置在你所需要的正確位置,還有就是 using語句的使用,它會自動銷毀stream對象,
當(dāng)然Stream.Close()大家都懂的
?
接著讓我們來說下關(guān)于流中怎么實(shí)現(xiàn)異步操作
在Stream基類中還有幾個關(guān)鍵方法,它們能夠很好實(shí)現(xiàn)異步的讀寫,
異步讀取 public virtual IAsyncResult BeginRead(byte[] buffer,int offset,int count,AsyncCallback callback,Object state) 異步寫 public virtual IAsyncResult BeginWrite( byte[] buffer, int offset, int count, AsyncCallback callback, Object state ) 結(jié)束異步讀取 public virtual int EndRead( IAsyncResult asyncResult ) 結(jié)束異步寫 public virtual void EndWrite( IAsyncResult asyncResult )大家很容易的就能發(fā)現(xiàn)前兩個方法實(shí)現(xiàn)了IAsyncResult接口,后2個end方法也順應(yīng)帶上了一個IAsyncResult參數(shù),
其實(shí)并不復(fù)雜,(必須說明下?每次調(diào)用?Begin方法時都必須調(diào)用一次 相對應(yīng)的end方法)
和一般同步read或write方法一致的是,他們可以當(dāng)做同步方法使用,但是在復(fù)雜的情況下可能也難逃阻塞崩潰等等,但是一旦啟用了
異步之后,這些類似于阻塞問題會不復(fù)存在,可見微軟對于異步的支持正在加大。
?
?最后是有關(guān)c#中Stream類和其子類的類圖
? 類圖呢?大家肯定會這么想把 ^^
? ?為什么這個在目錄中是灰色的?其實(shí)我個人覺得這個類圖不應(yīng)該放在這篇博文中,原因是我們真正理解并熟練操作了Stream的所有子類?(大牛除外)
? (這也是我寫后續(xù)文章的動力之一,寫博能很好的提升知識點(diǎn)的吸收,不僅能幫助別人,也能提高自己的對于知識點(diǎn)的理解),所以我想把類圖放在這
? ?個系類的總結(jié)篇中
?
本章總結(jié):
本章介紹了流的基本概念和c#中關(guān)于流的基類Stream所包含的一些重要的屬性和方法,關(guān)鍵是一些方法和屬性的細(xì)節(jié)和我們操作流對象時必須注意的事項,
文中很多知識點(diǎn)都是自身感悟?qū)W習(xí)而來,深夜寫文不容易,請大家多多關(guān)注下,下一章將會介紹操作流類的工具:StreamReader 和StreamWriter
敬請期待!
總結(jié)
以上是生活随笔為你收集整理的【转】C# 温故而知新:Stream篇(—)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 办高额信用卡需要哪些条件
- 下一篇: 【转】C#命名空间与java包的区别分析