1. 程式人生 > 實用技巧 >C# Stream篇(—) -- Stream基類

C# Stream篇(—) -- Stream基類

寫在前頭:

Stream系列文章共收錄7篇,本著備忘和歸納的目的本著備忘和歸納的目的,全部收錄於本分類中。

下面是有原文連線,望各位看官還是到原作者處學習,畢竟CV過來的文字難免有走樣之處。

原始連線:http://www.cnblogs.com/JimmyZheng/archive/2012/03/17/2402814.html

以後幾篇就不加此註釋了,見諒

-------------------------------------------------------------------- 分割線 -----------------------------------------------------------------------

目錄:

什麼是Stream?

什麼是位元組序列

Stream的建構函式

Stream的重要屬性及方法

Stream的示例

Stream非同步讀寫

Stream 和其子類的類圖

本章總結

什麼是Stream?

MSDN 中的解釋太簡潔了: 提供位元組序列的一般檢視

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

那什麼是位元組序列呢?

其實簡單的來理解的話位元組序列指的是:

位元組物件都被儲存為連續的位元組序列,位元組按照一定的順序進行排序組成了位元組序列

那什麼關於流的解釋可以抽象為下列情況:

打個比方:一條河中有一條魚遊過,這個魚就是一個位元組,這個位元組包括魚的眼睛,嘴巴,等組成8個二進位制,顯然這條河就是我們的核心物件:流

馬上進入正題,讓我們來解釋下c#的 Stream 是如何使用的

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

首先是建構函式

Stream 類有一個protected 型別的建構函式, 但是它是個抽象類,無法直接如下使用

   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();
}
}

可以看出系統自動幫我們實現了Stream 的抽象屬性和屬性方法

1: CanRead: 只讀屬性,判斷該流是否能夠讀取:

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

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

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

當我們使用流寫檔案時,資料流會先進入到緩衝區中,而不會立刻寫入檔案,當執行這個方法後,緩衝區的資料流會立即注入基礎流

MSDN中的描述:使用此方法將所有資訊從基礎緩衝區移動到其目標或清除緩衝區,或者同時執行這兩種操作。根據物件的狀態,可能需要修

改流內的當前位置(例如,在基礎流支援查詢的情況下即如此)當使用 StreamWriterBinaryWriter 類時,不要重新整理 Stream 基物件。

而應使用該類的 FlushClose 方法,此方法確保首先將該資料重新整理至基礎流,然後再將其寫入檔案。

(紅色部分為關鍵請大家務必能夠理解,今後會在相應的章節中介紹)

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

敬請期待!