stream 當你第一次用VB.NET讀寫檔案的時候,你肯定會發現VB.NET摒棄了傳統的檔案I/O支援,感覺不習慣。其實,在.NET裡面,微軟用豐富的“流”對象取代了傳統的檔案操作,而“流”,是一個在Unix裡面經常使用的對象. 我們可以把流當作一個通道,程式的的資料可以沿著這個通道”流”到各種資料存放區機構(比如:檔案,字串,數組,或者其他形式的流等)。為什麼我們會摒棄用了那麼久的IO操作,而代之為流呢?其中很重要的一個原因就是並不是所有的資料都存在於檔案中。現在的程式,從各種類型的資料存放區中擷取資料,比如可以是一個檔案,記憶體中的緩衝區,還有InterneT。而流技術使得應用程式能夠基於一個編程模型,擷取各種資料,而不必要學會怎麼樣去擷取遠程web伺服器上的一個檔案的具體技術。我們只需要在應用程式和web伺服器之間建立一個流,然後讀取伺服器發送的資料就可以了。 流對象,封裝了讀寫資料來源的各種操作,最大的優點就是一當你學好怎麼樣操作某一個資料來源時,你就可以把這種技術擴充到其他形形色色的資料來源。 流的種類流是一個抽象類別,你不能在程式中申明Stream的一個執行個體。在.NET裡面,由Stream派生出5種具體的流,分別是
FileStream 支援對檔案的順序和隨機讀寫操作MemoryStream 支援對記憶體緩衝區的順序和隨機讀寫操作NETworkStream 支援對Internet網路資源的順序和隨機讀寫操作,存在於System.Net.Sockets名稱空間CryptoStream 支援資料的編碼和解碼,存在於System.Security.Cryptography 名稱空間BufferedStream 支援緩衝式的讀寫對那些本身不支援的對象 並不是所有的Stream都採用用完全一摸一樣的方法,比如讀取本地檔案的流,可以告訴我們檔案的長度,當前讀寫的位置等,你可以用Seek方法跳到檔案的任意位置。相反,讀取遠程檔案的流不支援這些特性。不過,Stream本身有CanSeek, CanRead 和 CanWrite屬性,用於區別資料來源,告訴我們支援還是不支援某中特性。 下面我們簡單介紹一個FileStream類 FileStream類 進行本地檔案操作的時候,我們可以採用FileSteam類, 可以很簡單的讀寫為位元組數組(arrays of bytes)。對於單一資料型別的資料的讀寫,可以採用BinaryReader 和BinaryWriter以及StreamReader,StreamWriter類。 BinaryReader,用特定的編碼將基中繼資料類型讀作二進位值。BinaryWriter以二進位形式將基元類型寫入流,並支援用特定的編碼寫入字串。StreamReader/Writer則是把資料存放區為XML格式。在VB.NET裡面採用那個區別不大,因為所用的類都應用於兩種格式。VB.NET支援傳統的隨機讀寫檔案,你可以建立檔案,用於儲存Struct,然後根據記錄數訪問。就像在以前的Vb版本中一樣,用FileOpen,FileGet函數。很大程度上,這已經被XML或者資料庫取代。如果你建立新的應用程式,而有不需要考慮跟就版本的相容問題,建議採用.NET的新特性。不管你將要使用拿一個StreamClass,你都必須建立一個FileStream對象。有很多方式建立,最簡單就是指定檔案路徑,開啟模式,如下面的文法。 Dim fStream As New FileStream(path, fileMode, fileAccess)Path要包含檔案的路徑以及檔案名稱。fileMode是枚舉類型FileMode的成員之一,如下表所示。fileAccess是枚舉類型FileAccess的成員。Read (唯讀), ReadWrite (讀寫), and Write (寫操作)。決定了檔案的讀寫權限。
| 成員名稱 |
說明 |
| Append |
開啟現有檔案並尋找到檔案尾,或建立新檔案。 |
| Create |
指定作業系統應建立新檔案。如果檔案已存在,它將被改寫。 |
| CreateNew |
指定作業系統應建立新檔案。 |
| Open |
指定作業系統應開啟現有檔案。 |
| OpenOrCreate |
指定作業系統應開啟檔案(如果檔案存在);否則,應建立新檔案。 |
| Truncate |
指定作業系統應開啟現有檔案。檔案一旦開啟,就將被截斷為為零位元組大小。 |
當然,你也可以用 (Open, OpenRead, OpenText, OpenWrite)建立FileStream Dim FS As New FileStream = IO.File.OpenWrite("c:\Stream.txt") 另外一種方式開啟檔案可以用OpenFileDialog 和 SaveFileDialog控制項的OpenFile方法。不需要指定任何參數。 OpenFileDialog的OpenFile方法以唯讀方式開啟檔案; SaveFileDialog的OpenFile方法以讀寫方式開啟檔案。 FileStream只支援最基本的操作,把資料寫入位元組數組或者從位元組數組寫入檔案中。如果我們用FileStream把資料儲存在檔案中,首先把資料轉化為Byte數組,然後調用FileStream的Write方法。同樣,FileStream的Read方法,返回的也是位元組數組。你或許不會經常直接使用FileStream對象,我們還是有必要簡單看一下它的準系統建立FileStream對象之後,調用WriteByte 寫一個位元組到檔案中。 Write方法可以將一個數組寫入檔案中,需要三個參數 Write(buffer, offset, count)Buffer是要寫入數組地址,offset是位移量,count指寫入位元組數量,Read的文法也一樣。 由於FileStream要跟Bytes Array打交道,所以研究一下ASCIIEncoding 的GetBytes和UnicodeEncoding 的GetChars很有必要 下面的例子是一個轉換操作。 Dim buffer() As Byte Dim encoder As New System.Text.ASCIIEncoding() Dim str As String = "This is a line of text" ReDim buffer(str.Length - 1) Encoder.GetBytes(str, 0, str.Length, buffer, 0) FS.Write(buffer, 0, buffer.Length) 注意:必須Resize要寫入的Byte數組為要讀寫的長度。
靈活多樣的
IO
操作
有時候,在資料和位元組數組之間轉換是一件繁瑣的事情。為了避免這些無聊的轉換和簡化代碼,採用StreamReader/StreamWrite和BinaryReader/BinaryWriter不愧為明智之舉。StreamReader/StreamWrite分別由TextReader/TextWriter類派生,自動執行位元組編碼的轉換。BinaryReader/BinaryWriter由Stream派生,主要以二進位的形式讀寫資料。從二進位檔案讀資料的時候,首先建立一個BinaryReader的執行個體,BinaryReader的構建函數接受一個FileStream對象,代表將要讀的檔案。我們前面已經看過,可以用
File.OpenRead 或者
File.OpenWrite 方法建立FileStream對象。如下所示: Dim BR As New IO.BinaryReader(IO.File.OpenRead(path)) Dim BW As New IO.BinaryWriter(IO.File.OpenWrite(path)) BinaryWriter類有
Write和
WriteLine兩種方法,都可以接受任何類型的資料作為參數寫入檔案(
WriteLine在檔案尾追加一行資料)。BinaryReader類有很多讀資料的方法,資料存放區在檔案上的時候,並沒有任何關於自己類型的資訊,所以讀資料的時候,必須選擇合適的重載Read方法。 下面的例子假設BW是一個已經初始化過的BinaryWriter對象,表示如何寫一個字串、整數、雙精確度數字到檔案: BW.WriteLine("A String") BW.WriteLine(12345) BW.WriteLine(123.456789999999) 讀回資料的時候,必須選擇BinaryReader合適的Read方法: Dim s As String = BR.ReadString() Dim i As Int32 = BR.ReadInt32() Dim dbl As Double = BR.ReadDouble()對於文字檔,採用StreamReader/StreamWriter對象。方法跟上面差不多,寫資料同樣用Write和WriteLine方法。Read方法讀一個字元,ReadLine讀一行資料(直到有斷行符號/分行符號為止),ReadToEnd讀所有的字元,到檔案結束。
對象序列化
到目前為止,我們只是把簡單類型的資料寫到檔案中並讀回程式。而實際上,大多數的程式讀寫的資料可能並不是簡單類型,而是複雜的結構,例如:數組,數組列表,雜湊表等。於是,我們採取一種成為序列化的技術,首先把數組的值轉化為位元組序列,然後寫入檔案,這樣整個數組就儲存下來。相反,我們稱之為還原序列化。序列化是.NET的一個很大的話題,這列介紹一下基本的資訊。用BinaryFormatter的
Serialize 和
Deserialize方法把一個對象儲存到檔案和讀回程式。首先,imports System.RunTime.Serialization.Formatters,免得寫那麼長的申明。Formatters名空間包含了BinaryFormatter類,用於以二進位的資料序列化對象。建立BinaryFormatter執行個體,接著調用
Serialize方法,
Serialize接受兩個參數:一個是可寫的FileStream執行個體,用於儲存資料的檔案;另外一個是對象本身: Dim BinFormatter As New Binary.BinaryFormatter() Dim R As New Rectangle(10, 20, 100, 200) BinFormatter.Serialize(FS, R) BinaryFormatter的
Deserialize方法只有一個參數,FileStream執行個體。在當前位置,還原序列化得到一個類型不明的對象,我們必須用Ctype轉換為原來的對象。下面的例子還原序列化上面的檔案得到原來的Rectangle對象: Dim R As New Rectangle() R = CType(BinFormatter.Deserialize(FS), Rectangle)我們也可以以XmlFormatter進行對象序列化。首先在IDE的Project菜單選擇添加System.Runtime.Serialization.Formatters.Soap,然後就可以進行建立SoapFormatter對象了,方法跟BinFormatter一樣,只不過資料的儲存採用XML格式: Dim FS As New IO.FileStream("c:\Rect.xml", IO.FileMode.Create, IO.FileAccess.Write) Dim XMLFormatter As New SoapFormatter() Dim R As New Rectangle(8, 8, 299, 499) XMLFormatter.Serialize(FS, R)開啟c:\Rect.xml ,實際上裡面儲存了這些內容:
- <SOAP-ENV:Envelope xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="
http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="
http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="
http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="
http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="
http://schemas.xmlsoap.org/soap/encoding/">
- <SOAP-ENV:Body>
- <a1:Rectangle id="
ref-1" xmlns:a1="
http://schemas.microsoft.com/clr/nsassem/System.Drawing/System.Drawing%2C%20Version%3D1.0.3300.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Db03f5f7f11d50a3a">
<x>
8</x>
<y>
8</y>
<width>
249</width>
<height>
499</height>
</a1:Rectangle>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
檔案操作具體執行個體
在這一部分,你將找到更多常用的檔案操作的代碼執行個體。最常用、最基本的操作就是把text寫入檔案和讀回來。現在的應用程式通常不用二進位檔案作儲存簡單的變數,而用它來儲存物件,對象集合以及其他機器代碼。下面,將看到具體操作的例子。
讀寫文字檔
為了把text儲存到檔案,建立一個基於FileStream的StreamReader對象,然後調用Write方法把需要儲存的text寫入檔案。下面的代碼用SaveFileDialog提示使用者指定一個檔案,用於儲存TextBox1的內容。 SaveFileDialog1.Filter = _ "Text Files|*.txt|All Files|*.*" SaveFileDialog1.FilterIndex = 0 If SaveFileDialog1.ShowDialog = DialogResult.OK Then Dim FS As FileStream = SaveFileDialog1.OpenFile Dim SW As New StreamWriter(FS) SW.Write(TextBox1.Text) SW.Close() FS.Close() End If 同樣採用類似的語句,我們讀取一個文字檔,並把內容顯示在TextBox控制項中。StreamReader的ReadToEnd方法返迴文件的全部內容。 OpenFileDialog1.Filter = _ "Text Files|*.txt|All Files|*.*" OpenFileDialog1.FilterIndex = 0 If OpenFileDialog1.ShowDialog = DialogResult.OK Then Dim FS As FileStream FS = OpenFileDialog1.OpenFile Dim SR As New StreamReader(FS) TextBox1.Text = SR.ReadToEnd SR.Close() FS.Close() End If
各種對象的儲存
採用BinaryFormatte以二進位的形式,或者用SoapFormatter類以XML格式都可以序列化一個具體的對象。只要把所有BinaryFormatter的引用改為SoapFormatter,無需改變任何代碼,就可以以XML格式序列化對象。首先建立一個BinaryFormatter執行個體: Dim BinFormatter As New Binary.BinaryFormatter()然後建立一個用於儲存序列化對象的FileStream對象: Dim FS As New System.IO.FileStream("c:\test.txt", IO.FileMode.Create) 接著調用BinFormatter的
Serialize方法序列化任何可以序列化的framework對象: R = New Rectangle(rnd.Next(0, 100),rnd.Next(0, 300), _ rnd.Next(10, 40),rnd.Next(1, 9)) BinFormatter.Serialize(FS, R) 加一個Serializable屬性使得自訂的對象可以序列化 <Serializable()> Public Structure Person Dim Name As String Dim Age As Integer Dim Income As Decimal End Structure 下面代碼建立一個Person對象執行個體,然後調用BinFormatter的
Serialize方法序列化自訂對象: P = New Person() P.Name = "Joe Doe" P.Age = 35 P.Income = 28500 BinFormatter.Serialize(FS, P)你也可以在同一個Stream中接著序列化其他對象,然後以同樣的順序讀回。例如,在序列化Person對象之後接著序列化一個Rectangle對象: BinFormatter.Serialize(FS, New Rectangle(0, 0, 100, 200)) 建立一個BinaryFormatter對象,調用其
Deserialize方法,然後把返回的值轉化為正確的類型,就是整個還原序列化過程。然後接著發序列化Stream的其他對象。假定已經序列化了Person和Rectangle兩個對象,以同樣的順序,我們還原序列化就可以得到原來的對象:
Dim P As New Person() P = BinFormatter.Serialize(FS, Person) Dim R As New Rectangle R = BinFormatter.Serialize(FS, Rectangle)
Persisting Collections
集合的儲存
大多數程式處理對象集合而不是單個的對象。對於集合資料,首先建立一個數組(或者是其他類型的集合,比如ArrayList或HashTable),用對象填充,然後一個Serialize方法就可以序列化真箇集合,是不是很簡單?下面的例子,首先建立一個有兩個Person對象的ArrayList,然後序列化本身:
Dim FS As New System.IO.FileStream _ ("c:\test.txt", IO.FileMode.Create) Dim BinFormatter As New Binary.BinaryFormatter() Dim P As New Person() Dim Persons As New ArrayList P = New Person() P.Name = "Person 1" P.Age = 35 P.Income = 32000 Persons.Add(P) P = New Person() P.Name = "Person 2" P.Age = 50 P.Income = 72000 Persons.Add(P) BinFormatter.Serialize(FS, Persons)以儲存序列化資料的檔案為參數,調用一個BinaryFormatter執行個體的
Deserialize方法,就會返回一個對象,然後把它轉化為合適的類型。下面的代碼還原序列化檔案中的所有對象,然後處理所有的Person對象: FS = New System.IO.FileStream _ ("c:\test.txt", IO.FileMode.OpenOrCreate) Dim obj As Object Dim P As Person(), R As Rectangle() Do obj = BinFormatter.Deserialize(FS) If obj.GetType Is GetType(Person) Then P = CType(obj, Person) ' Process the P objext End If Loop While FS.Position < FS.Length - 1 FS.Close()下面的例子調用Deserialize方法還原序列化真箇集合,然後把傳回值轉換為合適的類型(Person): FS = New System.IO.FileStream("c:\test.txt", IO.FileMode.OpenOrCreate) Dim obj As Object Dim Persons As New ArrayList obj = CType(BinFormatter.Deserialize(FS), ArrayList) FS.Close()