Stream篇

本文轉自: http://www.cnblogs.com/JimmyZheng/archive/2012/03/17/2402814.htmlhtml

什麼是Stream?c#

什麼是字節序列數組

Stream的構造函數緩存

Stream的重要屬性及方法asp.net

Stream的示例異步

Stream異步讀寫async

Stream 和其子類的類圖函數

本章總結工具

 

 

 

什麼是Stream?學習

MSDN 中的解釋太簡潔了: 提供字節序列的通常視圖

(我可不想這麼理解,這一定讓我抓狂,我理解的流是向天然界的河流那樣清澈而又美麗,c#中的流也是同樣,許多技術或者說核心技術都須要流的幫忙)

那什麼是字節序列呢?

其實簡單的來理解的話字節序列指的是:

字節對象都被存儲爲連續的字節序列,字節按照必定的順序進行排序組成了字節序列

那什麼關於流的解釋能夠抽象爲下列狀況:

打個比方:一條河中有一條魚遊過,這個魚就是一個字節,這個字節包括魚的眼睛,嘴巴,等組成8個二進制,顯然這條河就是咱們的核心對象:流

立刻進入正題,讓咱們來解釋下c#的 Stream 是如何使用的

讓咱們直接溫故或學習下Stream類的結構,屬性和相關方法

首先是構造函數

Stream 類有一個protected 類型的構造函數, 可是它是個抽象類,沒法直接以下使用

   Stream stream = new Stream();

因此咱們自定義一個流繼承自Stream 看看哪些屬性必須重寫或自定義:

View Code

能夠看出系統自動幫咱們實現了Stream 的抽象屬性和屬性方法

   1:  CanRead: 只讀屬性,判斷該流是否可以讀取:

   2:  CanSeek: 只讀屬性,判斷該流是否支持跟蹤查找

   3:  CanWrite: 只讀屬性,判斷當前流是否可寫

*4: void Flush():這點必須說得仔細些:

    當咱們使用流寫文件時,數據流會先進入到緩衝區中,而不會馬上寫入文件,當執行這個方法後,緩衝區的數據流會當即注入基礎流

     MSDN中的描述:使用此方法將全部信息從基礎緩衝區移動到其目標或清除緩衝區,或者同時執行這兩種操做。根據對象的狀態,可能須要修

     改流內的當前位置(例如,在基礎流支持查找的狀況下即如此)當使用 StreamWriter 或 BinaryWriter 類時,不要刷新 Stream 基對象。

     而應使用該類的 Flush 或 Close 方法,此方法確保首先將該數據刷新至基礎流,而後再將其寫入文件。

(紅色部分爲關鍵請你們務必可以理解,從此會在相應的章節中介紹)

  5: Length:表示流的長度

*6: Position屬性:(很是重要)

雖然從字面中能夠看出這個Position屬性只是標示了流中的一個位置而已,但是咱們在實際開發中會發現這個想法會很是的幼稚,

不少asp.net項目中文件或圖片上傳中不少朋友會經歷過這樣一個痛苦:Stream對象被緩存了,致使了Position屬性在流中沒法

找到正確的位置,這點會讓人抓狂,其實解決這個問題很簡單,聰明的你確定想到了,其實咱們每次使用流前必須將Stream.Position

設置成0就好了,可是這還不能根本上解決問題,最好的方法就是用Using語句將流對象包裹起來,用完後關閉回收便可。

*7: abstract int Read(byte[] buffer, int offset, int count)

這個方法包含了3個關鍵的參數:緩衝字節數組,位移偏量和讀取字節個數,每次讀取一個字節後會返回一個緩衝區中的總字節數

第一個參數:這個數組至關於一個空盒子,Read()方法每次讀取流中的一個字節將其放進這個空盒子中。(所有讀完後即可使用buffer字節數組了)

第二個參數:表示位移偏量,告訴咱們從流中哪一個位置(偏移量)開始讀取。

最後一個參數:就是讀取多少字節數。

返回值即是總共讀取了多少字節數.

*8: abstract long Seek(long offset, SeekOrigin origin)

    你們還記得Position屬性麼?其實Seek方法就是從新設定流中的一個位置,在說明offset參數做用以前你們先來了解下SeekOrigin這個枚舉:

若是 offset 爲負,則要求新位置位於 origin 指定的位置以前,其間隔相差 offset 指定的字節數。若是 offset 爲零 (0),則要求新位置位於由 origin 指定的位置處。

若是 offset 爲正,則要求新位置位於 origin 指定的位置以後,其間隔相差 offset 指定的字節數.

Stream. Seek(-3,Origin.End);  表示在流末端往前數第3個位置

Stream. Seek(0,Origin.Begin); 表示在流的開頭位置

Stream. Seek(3,Orig`in.Current); 表示在流的當前位置日後數第三個位置

查找以後會返回一個流中的一個新位置。其實說道這你們就能理解Seek方法的精妙之處了吧

*9: abstract void Write(byte[] buffer,int offset,int count)

這個方法包含了3個關鍵的參數:緩衝字節數組,位移偏量和讀取字節個數

和read方法不一樣的是 write方法中的第一個參數buffer已經有了許多byte類型

的數據,咱們只需經過設置 offset和count來將buffer中的數據寫入流中

*10: virtual void Close()

關閉流並釋放資源,在實際操做中,若是不用using的話,別忘了使用完流以後將其關閉

這個方法特別重要,使用完當前流千萬別忘記關閉!

 

爲了讓你們可以快速理解和消化上述屬性和方法我會寫個示例而且關鍵部分會詳細說明

複製代碼
    static void Main(string[] args)
{
byte[] buffer = null;

string testString = "Stream!Hello world";
char[] readCharArray = null;
byte[] readBuffer = null;
string readString = string.Empty;
//關於MemoryStream 我會在後續章節詳細闡述
using (MemoryStream stream = new MemoryStream())
{
Console.WriteLine("初始字符串爲:{0}", testString);
//若是該流可寫
if (stream.CanWrite)
{
//首先咱們嘗試將testString寫入流中
//關於Encoding我會在另外一篇文章中詳細說明,暫且經過它實現string->byte[]的轉換
buffer = Encoding.Default.GetBytes(testString);
//咱們從該數組的第一個位置開始寫,長度爲3,寫完以後 stream中便有了數據
//對於新手來講很難理解的就是數據是何時寫入到流中,在冗長的項目代碼面前,我遇見過很
//多新手都會有這種經歷,我但願可以用如此簡單的代碼讓新手或者老手們在溫故下基礎
stream.Write(buffer, 0,3);

Console.WriteLine("如今Stream.Postion在第{0}位置",stream.Position+1);

//從剛纔結束的位置(當前位置)日後移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已經寫入了3個數據「Str」
stream.Write(buffer, (int)newPositionInStream, buffer.Length - (int)newPositionInStream);
}


//寫完後將stream的Position屬性設置成0,開始讀流中的數據
stream.Position = 0;

// 設置一個空的盒子來接收流中的數據,長度根據stream的長度來決定
readBuffer = new byte[stream.Length];


//設置stream總的讀取數量 ,
//注意!這時候流已經把數據讀到了readBuffer中
int count = stream.CanRead?stream.Read(readBuffer, 0, readBuffer.Length):0;


//因爲剛開始時咱們使用加密Encoding的方式,因此咱們必須解密將readBuffer轉化成Char數組,這樣才能從新拼接成string

//首先經過流讀出的readBuffer的數據求出從相應Char的數量
int charCount = Encoding.Default.GetCharCount(readBuffer, 0, count);
//經過該Char的數量 設定一個新的readCharArray數組
readCharArray = new char[charCount];
//Encoding 類的強悍之處就是不只包含加密的方法,甚至將解密者都能建立出來(GetDecoder()),
//解密者便會將readCharArray填充(經過GetChars方法,把readBuffer 逐個轉化將byte轉化成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();

}
複製代碼


顯示結果:

你們須要特別注意的是stream.Positon這個很神奇的屬性,在複雜的程序中,每每流對象操做也會很複雜,

必定要切記將stream.Positon設置在你所須要的正確位置,還有就是 using語句的使用,它會自動銷燬stream對象,

固然Stream.Close()你們都懂的

 

接着讓咱們來講下關於流中怎麼實現異步操做

在Stream基類中還有幾個關鍵方法,它們可以很好實現異步的讀寫,

複製代碼
異步讀取
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 )
結束異步讀取
public virtual int EndRead( IAsyncResult asyncResult ) 
結束異步寫
public virtual void EndWrite( IAsyncResult asyncResult )  
複製代碼

你們很容易的就能發現前兩個方法實現了IAsyncResult接口,後2個end方法也順應帶上了一個IAsyncResult參數,

其實並不複雜,(必須說明下 每次調用 Begin方法時都必須調用一次 相對應的end方法)

和通常同步read或write方法一致的是,他們能夠當作同步方法使用,可是在複雜的狀況下可能也難逃阻塞崩潰等等,可是一旦啓用了

異步以後,這些相似於阻塞問題會不復存在,可見微軟對於異步的支持正在加大。

 

 最後是有關c#中Stream類和其子類的類圖

  類圖呢?你們確定會這麼想把 ^^

   爲何這個在目錄中是灰色的?其實我我的以爲這個類圖不該該放在這篇博文中,緣由是咱們真正理解並熟練操做了Stream的全部子類?(大牛除外)

  (這也是我寫後續文章的動力之一,寫博能很好的提高知識點的吸取,不只能幫助別人,也能提升本身的對於知識點的理解),因此我想把類圖放在這

   個系類的總結篇中

 

本章總結:

本章介紹了流的基本概念和c#中關於流的基類Stream所包含的一些重要的屬性和方法,關鍵是一些方法和屬性的細節和咱們操做流對象時必須注意的事項,

文中不少知識點都是自身感悟學習而來,深夜寫文不容易,請你們多多關注下,下一章將會介紹操做流類的工具:StreamReader 和StreamWriter

敬請期待!

相關文章
相關標籤/搜索