0. 前言
在之前的章節中,大致介紹了C#中的一些基本概念,這篇我們將介紹一下C#的I/O操作,這將也是一個小連續劇,這是第一集,我們先來簡單了解一下C#中的I/O框架,
1. 什么是I/O
I/O 的全稱是input/output,翻譯過來就是輸入/輸出,對于一個系統或者計算機來說,鍵盤、U盤、網路介面、顯示幕、音響、攝像頭等都是IO設備,那么,對于一個程式I/O又是什么呢?
對于程式而言,I/O就是與外界進行資料交換的方式,借用一句廣告詞,程式不生產資料,只是資料的搬用工,當然,正如XX還需要對水進行過濾、消毒等工序一樣,程式也要對資料進行運算,所以也不完全算是搬用工,嚴格來講是加工廠,那么,I/O就是工廠的原料提供商和成品銷售商,
在C# 中,I/O體系整體分為三個部分,后臺存盤流、裝飾器流、流配接器,具體劃分如下圖所示:

在流與流之間,都是采用位元組資料進行交換,所以可以得到一個簡單的結論,I/O在程式中表現為位元組流,換句話說I/O就是將各種資料轉成位元組的工具,
3. Stream 基類
C#中,所有流都是繼承自Stream類,Stream類定義了流應該具有的行為和屬性,使得開發人員可以忽略底層的作業系統和基礎設備的具體細節,C#對流的處理忽略了讀流和寫流的區別,使其更像是一個管道,方便資料通信,流涉及到三個基本操作:
- 讀取 - 將資料從流中傳輸到資料結構中
- 寫入 - 將資料從資料源寫入流中
- 查找 - 對流中操作的當前位置進行查找和修改
因為流的特性,可能并不是所有的流都支持這三種操作,所以Stream提供了三個屬性,以方便確認流是否支持這三種操作:
public abstract bool CanRead { get; } // 獲取指示當前流是否支持讀取的值
public abstract bool CanWrite { get; } // 獲取指示當前流是否支持寫入功能的值
public abstract bool CanSeek { get; } // 獲取指示當前流是否支持查找功能的值
以上這三個屬性均由子類根據自身特性確認是否支持讀取、寫入、查找,可能三個屬性不會都為true,但絕對不會都為false,
下面是一些常見的流:
- FileStream 用來操作檔案的流
- MemoryStream 操作記憶體的流
- BufferedStream 快取流,用來增強其他流的操作性能
- NetworkStream 使用網路套接字進行操作的流
- PipeStream 通過匿名和命名管道進行讀取和寫入
- CryptoStream 用于將資料流鏈接到加密轉換
4. 操作
C# 中I/O的操作都屬于System.IO這個命名空間,在這個命名空間中C# 定義了檔案相關的類、各種流、裝飾器流、配接器以及其他一些相關的結構體,在以System.IO開頭的命名空間中,C#對IO進一步擴展,并提供了流壓縮和解壓縮(System.IO.Compression),搜索和列舉檔案系統元素(System.IO.Enumeration),提供用于使用記憶體映射檔案的類(System.IO.MemoryMappedFiles)等內容,
我們先略過之后篇幅會介紹的內容不提,先來看一下Stream類里重要的屬性和方法:
1. 流里資料的長度
public abstract long Length { get; }
當Stream物件的CanSeek為true時,也就是流支持搜索的時候,可以通過這個屬性確認流的長度,也就是有多少個位元組的資料,
2. 流的位置
public abstract long Position { get; set; }
同長度的前提條件一致,當Stream物件支持搜索的時候,可以通過該屬性確認流的位置或者修改流的位置,
3. 讀取流里的資料
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,
4. 把資料寫入流
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);
這個方法就更簡單了,直接寫一個位元組給流,
5. 關倍訓銷毀流
流在操作完成之后,需要將其關閉以釋放流所持有的檔案或IO設備等資源,很多人在使用電腦的時候,不能用QQ發送在本地已經打開的excel檔案,它會提示檔案被占用無法傳輸,這就是因為Excel打開了這個檔案,就持有一個檔案相關的流,所以QQ無法發送,解決辦法很簡單,關掉excel軟體即可,回到當前,也就是我們在使用完成之后必須關閉流,
那么我們該如何關閉流呢?呼叫以下方法:
public virtual void Close ();
C#雖然設定了Close方法,但是并不支持開發者在撰寫程式的時候手動呼叫Close方法,更推薦使用:
public void Dispose ();
這個方法會將釋放流所持有使用的資源,并關閉流,
當前需要注意的一個地方是,在把流關倍訓釋放之前把流里的資料推送到基礎設備,即呼叫:
public abstract void Flush ();
有一些流設定了自動推送功能,如果遇到這種流則不需要手動呼叫該方法,
對于流來說,一旦銷毀或關閉,這個流就無法二次使用了,所以呼叫了Close、Dispose之后再次嘗試讀取/寫入流都會報錯
5. 本篇總結以及下篇預告
本篇內容大概介紹了一下C#的IO體系以及一些基本操作,下一篇將介紹如何操作檔案,
更多內容煩請關注我的博客

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/44836.html
標籤:C#
上一篇:DFA演算法C#實作
