C 基礎知識系列 14 IO篇之入門IO

2021-10-12 21:03:30 字數 3686 閱讀 5301

前言

在之前的章節中,大致介紹了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...