前言
在之前的章節中,大致介紹了c#中的一些基本概念。這篇我們將介紹一下c#的i/o操作,這將也是乙個小連續劇。這是第一集,我們先來簡單了解一下c#中的i/o框架。
什麼是i/o
i/o 的全稱是input/output,翻譯過來就是輸入/輸出。對於乙個系統或者計算機來說,鍵盤、u盤、網路介面、顯示器、音響、攝像頭等都是io裝置。那麼,對於乙個程式i/o又是什麼呢?
對於程式而言,i/o就是與外界進行資料交換的方式。借用一句廣告詞,程式不生產資料,只是資料的搬用工。當然,正如xx還需要對水進行過濾、消毒等工序一樣,程式也要對資料進行運算,所以也不完全算是搬用工,嚴格來講是加工廠。那麼,i/o就是工廠的原料提供商和成品銷售商。
在c# 中,i/o體系整體分為三個部分,後台儲存流、裝飾器流、流介面卡,具體劃分如下圖所示:
在流與流之間,都是採用位元組資料進行交換,所以可以得到乙個簡單的結論,i/o在程式中表現為位元組流,換句話說i/o就是將各種資料轉成位元組的工具。
stream 基類
c#中,所有流都是繼承自stream類,stream類定義了流應該具有的行為和屬性,使得開發人員可以忽略底層的作業系統和基礎裝置的具體細節。c#對流的處理忽略了讀流和寫流的區別,使其更像是乙個管道,方便資料通訊。流涉及到三個基本操作:
讀取 - 將資料從流中傳輸到資料結構中
寫入 - 將資料從資料來源寫入流中
查詢 - 對流中操作的當前位置進行查詢和修改
因為流的特性,可能並不是所有的流都支援這三種操作,所以stream提供了三個屬性,以方便確認流是否支援這三種操作:
public abstract bool canread // 獲取指示當前流是否支援讀取的值
public abstract bool canwrite // 獲取指示當前流是否支援寫入功能的值
public abstract bool canseek // 獲取指示當前流是否支援查詢功能的值
以上這三個屬性均由子類根據自身特性確認是否支援讀取、寫入、查詢,可能三個屬性不會都為true,但絕對不會都為false。
下面是一些常見的流:
我們先略過之後篇幅會介紹的內容不提,先來看一下stream類裡重要的屬性和方法:
流裡資料的長度
public abstract long length
當stream物件的canseek為true時,也就是流支援搜尋的時候,可以通過這個屬性確認流的長度,也就是有多少個位元組的資料。
流的位置
public
abstract
long position
同長度的前提條件一致,當stream物件支援搜尋的時候,可以通過該屬性確認流的位置或者修改流的位置。
讀取流裡的資料
public abstract int read (byte buffer, int offset, int count);
public virtual int readbyte ();
這是兩種不同的讀取方式,第一種是每次讀取多個位元組的資料,第二個是每次唯讀乙個位元組的資料。這裡來細細講解一下區別:
public
abstract
int read (
byte
buffer,
int offset,
int count)
;
表示流每次最多讀取count個位元組的資料,然後將資料放到buffer中,位置從下標為offset開始,並返回實際讀取的位元組數,如果流已經讀完了,則返回0。這個過程中,position會後移實際讀取長度,如果流支援搜尋,程式中可以呼叫這個屬性。
所以這裡就有會這樣的乙個限制:offset + count <= buffer.length,換句話說,偏移量 + 最大讀取數目不能大於快取陣列的長度。
因為這個方法返回乙個實際讀取長度,可能有人會這樣判斷是否讀完:根據返回的結果與count比,如果返回的長度小於count則認為流已經讀完;否則流還沒讀完。
有一些流可能會達成這樣的效果,但是很多流並不能以此為依據來判斷流是否讀完,也許某一次讀取長度小於count,然後再讀一次發現又有資料了。這是因為io在系統中屬於高耗時操作,大部分情況下io的效能和程式的運算速度相差甚遠。所以經常會出現這樣的情景:流的長度是100,給了長度為100的快取位元組陣列,然後第一次讀取了10個位元組,第二次讀取了5個位元組,這樣一點一點的把這100個位元組讀取到。
所以,必須以返回值為0作為流的讀完判斷依據。
public
virtual
int readbyte (
);
這個方法很簡單,每次從流裡讀取乙個位元組的資料,如果讀取完成返回-1。可能有人會疑惑了,這個方法明明是讀取乙個位元組,也就是個byte,那為什麼返回型別是int呢?很簡單,因為byte沒有負數,而int有。所以,當返回值不等於-1的時候,可以放心的型別轉換為byte。
把資料寫入流
public
abstract
void write (
byte
buffer,
int offset,
int count)
;public
virtual
void writebyte (
byte
value
);
流的寫入與讀取相比就簡單多了,至少我們不用判斷流的位置。現在簡單分析一下:
public abstract void write (byte buffer, int offset, int count);
表示從buffer的offset下標開始,取count個位元組寫入流裡。所以,對offset、count的限制依舊,和不能大於陣列的長度。寫入成功,流的位置會移動,否則將保持現有位置。
public
virtual
void writebyte (
byte
value
);
這個方法就更簡單了,直接寫乙個位元組給流。
關閉或銷毀流
流在操作完成之後,需要將其關閉以釋放流所持有的檔案或io裝置等資源。很多人在使用電腦的時候,不能用qq傳送在本地已經開啟的excel檔案,它會提示檔案被占用無法傳輸。這就是因為excel開啟了這個檔案,就持有乙個檔案相關的流,所以qq無法傳送。解決辦法很簡單,關掉excel軟體即可。回到當前,也就是我們在使用完成之後必須關閉流。
那麼我們該如何關閉流呢?呼叫以下方法:
public
virtual
void close (
);
public
void dispose (
);
這個方法會將釋放流所持有使用的資源,並關閉流。
當前需要注意的乙個地方是,在把流關閉或釋放之前把流裡的資料推送到基礎裝置,即呼叫:
public
abstract
void flush (
);
有一些流設定了自動推送功能,如果遇到這種流則不需要手動呼叫該方法。
對於流來說,一旦銷毀或關閉,這個流就無法二次使用了,所以呼叫了close、dispose之後再次嘗試讀取/寫入流都會報錯
C 基礎知識系列之 for迴圈
c 的for迴圈提供的迭代迴圈機制是在執行下一次迭代前,測試是否滿足某個條件,其語法如下 for initializer,condition,iterator statement s 其中 initializer是指在執行第一次迭代前要計算的表示式 通常把乙個區域性變數初始化為迴圈計數器 condi...
C 基礎知識系列之 for迴圈
c 的for迴圈提供的迭代迴圈機制是在執行下一次迭代前,測試是否滿足某個條件,其語法如下 for initializer,condition,iterator statement s 其中 initializer 是指在執行第一次迭代前要計算的表示式 通常把乙個區域性變數初始化為迴圈計數器 cond...
C 基礎知識篇
1.命名空間 在c 中,識別符號 name 可以是符號常量 變數 巨集 函式 結構 列舉 類和物件等。為了避免在大規模程式設計中以及在程式設計師使用各種各樣的c 庫時,這些識別符號的命名發生衝突,標準c 引入了關鍵字namespace 命名空間 以便更好控制識別符號作用域。定義格式如下 namesp...