掌握Streams、Readers和Writers

發表于:2007-05-25來源:作者:點擊數: 標簽:StreamsReadersWriters掌握
你可以用.NET Streams、Readers和Writers來訪問系統文件,并通過 網絡 傳輸數據以及高效地處理XML文檔。 by Enrico Sabbadin 技術工具箱:VB.NET, ASP.NET, XML .NET Framework Base Class Library (BCL) 從各方面增加了對VB 程序員 的支持。(一些MSDN文檔
你可以用.NET Streams、Readers和Writers來訪問系統文件,并通過網絡傳輸數據以及高效地處理XML文檔。
by Enrico Sabbadin
技術工具箱:VB.NET, ASP.NET, XML

.NET Framework Base Class Library (BCL) 從各方面增加了對VB程序員的支持。(一些MSDN文檔引用該Library作為Framework Class Library [FCL])。你可以通過它編寫出更加簡潔的代碼,用于從數據訪問和Web編程方面的問題 –- 然而它并不是隨隨便便地拿來就能用的。你必須掌握幾種新的概念并將其運用到你的編程技巧中,同時學習用新的方法來處理普遍的任務,比如讀寫文件或是處理XML文檔。用于執行這些任務的Stream、Reader和Writer類及其子類的運用在.NET Framework中是相當普遍的。

Stream、Reader和Writer的應用在VB開發中并不少見?;蛟S你在用ADO庫時用到過Stream類,或者你在開發基于SAX的XML應用程序時用到過Reader和Writer類。而如果你要研究一些更深的課題(比如TCP socket)則需要對它們有更詳細的了解。我將講解Stream、Reader和Writer是如何相互關連的、它們不同的用法,以及如何運用它們來更高效地完成一些基本和高級的任務。

圖1.
圖1. javascript:openWindowRes('DotNetMagazine/2003_01/xml2html.asp?xmlfile=StreamReaderWriter/Figure1.xml&xslfile=../../include/xsl/Figure.xsl');">看箭頭讀圖
這三個類的關系是相當對稱的(見圖1)。Stream是.NET Framework中的抽象類,它提供一個通用的接口用來實現用統一編程訪問指定輸入/輸出(I/O)設備(如文件或TCP sockets)。這樣就使你和操作系統及內部儲備的細節分隔開了。

Stream通過一個字節數組(byte array)執行對讀和寫的操作。Read和Write方法的字面意思是基于指針(cursor)模式的。Stream類中包含一個內置的pointer,用來指向最后讀取或書寫的字節。之后對Read或Write的調用以指針的當前位置+1開始。如果調用成功,則在Stream中的指針會按書寫或讀取字節的個數將當前位置向后移動。

除了一個字節數組,你還需要將一個offset和count參數傳入Read和Write方法中來控制數據,這些數據必須是從該數組中被復制到底層設備(Write)的,或者從底層設備被復制到數組中(Read)的。offset參數允許你指定字節數組的起始位置并以此作為起點開始讀取或寫入;count參數用來指定讀取或寫入Stream的最多字節的個數。Read方法會返回復制到字節數組中的字節個數,其最終結果會少于count參數允許的字節個數(即使你需要更多的字節也不會被拋出異常):

clearcase/" target="_blank" >cc>
Dim s As New FileStream("c:\file.txt", _
   FileMode.OpenOrCreate)
Dim b(15) As byte
'read 3 bytes and put them in byte array's
'position 5, 6 and 7
Dim byteread As Integer = s.Read(b, 5, 3)
'now the stream cursor is pointing to the 4th 
'position in the stream
'write the 6th and 7th bytes to the Stream at
'Stream position 4 and 5
s.Write(b, 6, 2)

五種可選的Streams類
BCL 提供五種Stream類的具體實現 (concrete implementation)(見表1)。FileStream、NetworkStream和CryptoStream類分別用來訪問系統文件(local file system)、TCP 設計和加密服務(cryptographic service)。MemoryStream和BufferedStream類是基于內存的(memory-based)的Stream,它們在執行一些特定程序任務(application-specific tasks)時會相當方便,并且它們還會通過NetworkStreams優化你的讀寫過程。

NetworkStream和CryptoStream類提供了forward-only功能,而FileStream類和MemoryStream類卻能使pointer來回運作。BufferedStream類的指針行為(cursor behavior)是不固定的,因為它是由在構造器中和BufferedStream對象相關的底層Stream來決定的。

你可以用CanSeek屬性來測定該底層設備是不是forward-only的,如果其返回值為true,那么你就可以用Position屬性或者Seek方法將指針定位到一個特定的位置了。Length屬性會告訴你Stream中包含多少個字節。

FileStream類用來提供文件的只讀功能。只要你輸入文件的路徑,你就可以訪問該文件的內容了:

'read the whole file content into a byte array
Dim fsstream As New FileStream("c:\x.bin", _
   FileMode.Open)
Dim readbytes(fsstream.Length - 1) As byte
Dim readbytes As Integer = _
   fsstream.Read(readbytes, 0, fsstream.Length)

你還能夠用File類的Create、Open、OpenRead和OpenWrite等靜態方法來獲得一個FileStream類(File類中所有的方法都是靜態的)。

FileInfo類使用了和File類獲得FileStream對象相同的方法,只不過在FileInfo類中的Create、OpenRead和OpenWrite都是基于實例(instance-based)的方法。根據Microsoft的文檔說明,這兩個類是可以互換的。但出于安全方面的原因,該文檔建議當你需要在相同對象中進行不同的操作時最好使用FileInfo類 。

FileInfo類還提供其他一些用于操作系統文件結構的方法,比如文件的復制和目錄的重命名。這使該類繼承了在VB6 中我們所喜愛的FileSystemObject類的一些特性。

當你要訪問包含二元數據的文件時,FileStream類就會派上用場,但在處理基于文本的文件時不要使用它。我下面要介紹的StreamReader類和StreamWriter類是專門用來處理此類問題的。

FileStream類不會立即將每個單一字節寫入底層設備中,它有一個用來緩存數據的內部緩沖器(buffer)。數據只有在緩沖器被填滿的情況下才會進入系統文件中。如果你沒有在FileStream中指定緩沖器的大?。╯ize),則它的缺省大小是4K(這在通常情況下足夠了)。你最好用其中一種重載構造器來定制該緩沖器的大小,當你需要執行大的I/O操作時,構造器會將緩沖器的大小它作為參數傳入。優化緩沖器的大小意味著你要在頻繁地訪問底層系統文件時找到一個平衡點,以使內存的使用量和性能都不受影響。

設置緩沖器的大小
一般來說,緩沖器大小的值應該是讀取或寫入Stream數據大小的二十分之一。這只是一個粗略的算法,你應該在程序中自己測試這個值:

fs = New FileStream("c:\aaa.txt", FileMode.Open, _
   FileAccess.Write, FileShare.Read, _
   inputsize/20)

通過調用Flush方法,你可以在緩沖區接近最大容量之前強制寫入底層Stream。

遺憾的是,你不能在NetworkStream類中對緩沖器的大小進行設置。每一個單一的被寫入的字節會被立即發送出去。在某些情況下,這會是影響網絡應用程序的一個主要因素。 BufferedStream類可以將Stream對象串連起來解決這個問題。新建一個NetworkStream對象并將它作為創建BufferedStream對象的一個參數。你可以在BufferedStream構造器中指定緩沖器的大小。

這樣,你就可以用BufferedStream對象來寫數據了,當內部緩沖器接近最大容量時它只會將數據堆存到NetworkStream中去。以下代碼教你如何使用這個簡單卻有效的技巧:

Dim clientsocket As New TcpClient("localhost", _
   8080)
Dim ns As networkstream = clientsocket.GetStream()
Dim bfstream As New BufferedStream(ns, 2048)
Dim i As Integer
'writes a single byte on each call
For i = 0 To 100
   bfstream.Write(New byte(0) {i}, 0, 1)
Next
'since the internal buffer (2048 size) has not 
'been filled up yet, data is sent to the network
'stream only when flush is called
bfstream.Flush()
bfstream.Close()

MemoryStream提供了一種方法來控制何時發送數據以及有多少數據被發送到NetworkStream。你可以建立一個MemoryStream對象并將數據寫進去,然后在合適的時間調用WriteTo方法,并將NetworkStream以一個參數形式傳入。MemoryStream對象會將內部所有的數據堆存到已有的Stream當中:

Dim clientsocket As New TcpClient("localhost", _
   8080)
Dim ns As NetworkStream = clientsocket.GetStream()
Dim memstr As New MemoryStream()
Dim i As Integer
'writes a single byte on each call
For i = 0 To 100
   memstr.Write(New byte(0) {i}, 0, 1)
Next
'write all data in the network Stream
memstr.WriteTo(ns)

使用Streams的另一個好處是你可以用它來優化ASP.NET頁面的性能。ASP.NET 的Response對象允許通過使用底層的NetworkStream的OutputStream屬性來將頁面響應發送回瀏覽器。在購置一個頁面響應時,你可以利用它來減少內存分配(避免產生臨時的字符串)。

可以考慮使用這個普通的任務,即通過XSLT將XML轉換為HTML頁面發送回去。在你試過了不同XslTranform類的Transform方法之后,你就可以以此作為結束:

Dim x As New Xsl.XslTransform()
x.Load(<xslfilepath>)
Dim a As New XPath.XPathDocument(<xmlfilepath>)
'The StringWriter is just a wrapper on a string 
'object
Dim s As New StringWriter()
x.Transform(a.CreateNavigator, Nothing, s)
Response.Write(s.ToString())

這樣做的效果不是很好,因為它會建一個毫無必要的臨時文件。你可以通過將底層Response對象的NetworkStream傳入Transform方法來更好地執行這個任務:

Dim x As New Xsl.XslTransform()
x.Load(<xslfilepath>)
Dim a As New XPath.XPathDocument(<xmlfilepath>)
x.Transform(a.CreateNavigator, Nothing, _
   Response.OutputStream)

當你需要將二元的、不基于文本的數據發送到瀏覽器中時,使用Response.OutputStream也是一個好辦法:

Dim f As New FileStream(Server.MapPath("") + _
   "\help.gif", FileMode.Open)
Dim bt(f.Length - 1) As byte
f.Read(bt, 0, fs.Length)
Response.ContentType = "image/gif"
Response.OutputStream.Write(bt, 0, f.Length)

游弋在字節和字符串之間
在很多種情況下,你需要將基于文本的數據寫入一個Stream或者從Stream中讀取數據。此時如果在TextReaders 和TextWriters中發現一個簡短的、封裝好的Stream就再好不過了。然而你并不一定非得使用這些類,因為.NET Framework中的System.Text.Encoding類就能夠使你輕易地將字節數組編譯到字符串中去,或是將字符串反編譯到字節數組中來。GetBytes方法能夠將一個字符串看作是一個輸入和輸出的字節數組;而GetString方法則正好相反。無論使用何種方法,你都必須提供一個編碼(ASCII、UTF8、Unicode,等等),用來定義何種特性會被映射到數字型字節的值中(見列表1)。

除了可以用字節數組和字符串來寫Stream之外,.NET BCL還支持formatter類。你可以通過Serialize和Deserialize方法來調用formatter:

Dim a As New Class1()
a.DateOfBirth = New Date(2000, 12, 12)
a.Name = "John"
Dim fm As New Formatters.Binary.BinaryFormatter()
fm.Serialize(bsw, a)

BCL目前支持兩種formatter:Binary formatter和 SOAP formatter。如前段代碼所顯示的,在使用formatter時你可以不受基類型(base type)的限制。

你可以將任意對象序列化(Serialize)或反序列化(Deserialize)到一個Stream中去,只要你以<Serializable>屬性注明該類(見列表2)。當你要開發一個分布式的、松散耦合(Loosely-coupled)的程序時,用FileStream類或通過NetworkStream類來持續一個對象狀態是一個很棒的方法,使用SOAP formatter,你甚至可以將一個對象實例以一個郵件附件的形式發送出去。

正如你在前面的例子中看到的,Stream編程是一個針對低層次的任務。你的代碼通常處理的是一些比字節數組更高層次的實體:如字符串、整數、對象等等。因而Formatter提供的幫助是不可估量的。但是在你每次需要將所有基類型轉換為字節數組時,formatter要求使用大量能夠重用的、error-prone的代碼。 這時便可以用Readers和Writers來解決了(但你還是得用formatter來解決對象的序列化)。

我先來講解一下BinaryReader和BinaryWriter類。這兩個類都在構造器中使用了Stream,而且能夠使你分別從相關的Stream中讀取基本數據(盡管還沒有被寫入文檔,但這兩個類中很可能會使用BinaryFormatter)。這兩個類并非完全對應的,用于不同類型的Write操作被用在一組Write方法的重載中;BinaryReader 類中用到的Read操作被用于實現不同的方法,每一種方法對應一種數據類型:

Dim fsstream As FileStream
fsstream = File.Create("c:\formatter.bin")
Dim bw As New BinaryWriter(l_fsstream)
Dim varin As Long = 1
bw.Write(varin)
fsstream.Close()
fsstream = File.OpenRead("c:\formatter.bin")
Dim br As New BinaryReader(fsstream)
Dim varout As Long = br.ReadInt64()

TextReaders類和TextWriters類都是專門用于解決字符和字符串讀寫操作的抽象類。BCL 提供一個TextReader的兩種具體實現類:StringReader類和StreamReader類。由于StringReader類并不是特別有用,所以在這里我就不詳細介紹它了。

指定StreamReader參數
StreamReader構造器接受一個Stream對象或者一個文件路徑(你可以使用Universal Naming Convention [UNC] 路徑,但不能用URL)。你還可以指定這些參數:比如編碼類型(encoding type)(如果沒有特別指定,系統會默認使用UTF8編碼);作為緩存的內置緩沖器大?。ê芸赡芡ㄟ^BufferedStream對象來實現);還有一個布爾值,它用來指示是否應通過該Stream的第一個字節來判斷編碼類型。

可以看到,StreamReader是個很有用的封裝(wrapper)類,其中所包含的功能你也可以用前面提及的Stream和Formattter類來完成。

StreamReader類使用不同的Read操作。你可以讀取單行字符串(用ReadLine方法)或是底層Stream的所有內容(ReadtoEnd方法);你可以指定字符的個數,通過Read或ReadBlock方法來讀?。ù藭r會返回一個char的數組);你還可以用Peek方法來檢測該Stream是否被讀完。以下代碼顯示如何實現一個簡單的客戶端應用程序,該程序運用了StreamReader和NetworkStream類在Web服務器上執行荷載應力測試

Dim tcpClient As New _
   Net.Sockets.TcpClient( _
   "www.sitetostress.com", 80)
Dim networkStream As _
   System.Net.Sockets.NetworkStream = _
   tcpClient.GetStream()
Dim strmwrt As New _
   StreamReader("c:\mycommands.txt")
Do While Not strmwrt.Peek = -1
Dim sbytes As [byte]() = _
   System.Text.Encoding.ASCII.Getbytes( _
   strmwrt.ReadLine())
networkStream.Write(s字節s, 0, sbytes.Length)
Loop
strmwrt.Close()

一組HTTP命令是通過StreamReader對象從一個文件中讀取的,它被Encoding類轉換到一個字節數組中,然后被發送到Web服務器上。

現在來看看這些將書寫功能封裝到Stream中的類。它們的基本抽象類(base abstract class)是TextWriter類,BCL提供了它的五種繼承類(見表2)。這里我將主要介紹StreamWriter類和StringWriter類。

StreamWriter類采用Stream或文件名以及相同的參數名作為StreamReader類的構造器。 StreamWriter類使用了兩個重載方法來寫入底層Stream: Write和WriteLine(WriteLine方法在最后加了一個回車鍵)。兩種方法都提供很多的重載形式來接受所有.NET基本類型,這看起來很象BinaryWriter類的Write方法;區別在于,數字類型以字符串形式(與區域設置相關)被寫入底層設備。

如果你需要同時讀取和寫入文本文件,那么你可以使用TextReader和TextWriter這些方便的性能,而不要用FileStream對象來處理字節數組。用FileStream對象來打開文件,將它以TextReader和TextWriter的構造器參數來傳入并運行它。記住Reader和Writer使用相同的Stream指針,并在每次寫入TextWriter時調用Flush以使TextReader讀取你寫入的內容:

Dim fsstream As New FileStream("c:\x.txt", _
   FileMode.Create)
'Associate the same stream to a StreamReader and 
'a StreamWriter
Dim sr As New StreamReader(fsstream)
Dim sw As New StreamWriter(fsstream)
sw.WriteLine("mytext")
'flush data to the Stream
sw.Flush()
'move cursor to the beginning 
fsstream.Position = 0
Dim s As String = sr.ReadLine()
'This will dump "mytext" to the debugger
Debug.WriteLine(s)

字符串連接
StringWriter類提供一個類似于Stream的方法連接字符串。字符串連接是一個開銷很大的操作,因為字符串本身是不變的(immutable)類型。來看看這行代碼:

mystring = mystring + "my string"

這里mystring被賦予了一個新值,并申請了新的空間來保存連接結果字符串。

用StringWriter來連接字符串會提供更好的性能。StringWriter類只是另一個對象 -- StringBuilder 的封裝,StringBuilder才是真正用來執行字符串連接的類。你可以將StringBuilder作為一個參數構造器傳入StringWriter (否則會在其內部生成一個實例),并用GetStringBuilder方法返回StringBuilder。然而StringBuilder 類會通過Remove、Insert和Replace方法來提供一些額外的性能(見資源中關于StringBuilder對象其他功能的介紹)。

.NET中的XML類也使用了Streams、Readers和Writers。XmlReaders和XmlWriters抽象類類似于TextReaders和TextWriters。XmlWriter是包含在XmlTextWriter中的具體實現。你可以將一個Stream、TextWriter或XML文檔文件路徑傳入構造器。以下代碼用 XmlTextWriter將XML document寫入NetworkStream:

s = New TcpListener(8080)
s.Start()
Dim tcpClient As TcpClient = s.AcceptTcpClient()
Dim ns As NetworkStream = tcpClient.GetStream()
Dim xmltextwr As New Xml.XmlTextWriter(ns, _
   Encoding.UTF8)
xmltextwr.WriteStartDocument()
xmltextwr.WriteStartElement("myroot")
xmltextwr.WriteStartElement("child")
xmltextwr.WriteAttributeString("myattr", _
   "my attr value")
xmltextwr.WriteString("My value")
xmltextwr.WriteEndElement()
xmltextwr.WriteEndElement()
xmltextwr.WriteEndDocument()
xmltextwr.Flush()
xmltextwr.Close()

network Stream用一個XmlTextWriter連接引入客戶端的連接請求。調用XmlTextWriter對象中的WriteXXX將相應的XML文本寫入TCP連接。

XmlReader抽象類有三種具體實現,和上下文相關的是XmlTextReader類,它從構造器中讀取文件路徑、Stream或是TextReader。當你將XmlTextReader實例初始化之后,你便可以用forward-only或read-only等指針模式來操作XML文檔元素了。XmlDocument類(該類代表了W3C XML Document Object Model [DOM] 在.NET Framework中的實現)也采用Stream、TextReader或是XmlReader類作為重載load方法的輸入參數(見列表3)。

關于在.NET Framework BCL當中對I/O對象的簡要介紹差不多就這么多了。你可能會發現我跳過了VB.NET部分 –- 在Microsoft Visual Basic .NET runtime assembly中用到的特定對象。這些assembly reference會被自動加入到所有新的VB項目中。assembly提供一套可供選擇的方法來訪問和處理I/O 操作。我的想法是,如果你正開始一個新的.NET項目,那么你應該研究一下BCL類,而不僅僅是I/O操作。這會對你將來更輕松地應用其他.NET語言打好基礎。


圖1.看箭頭讀圖

從圖中可以看到這個類的整體結構布局、各種關系,以及所有包含在.NET Framework中的寫入文檔的Stream、Reader和Writer對象。箭頭指向的類表示該類的構造器認可箭頭起始方向的類。你還可以看到構造器何時認可一個文件路徑。

VB.NET / 游弋在字節和字符串之間

列表1.Encoding類的GetBytes方法預置了一個HTTP request,然后用GetString方法將response從一個字節數組轉換到一個字符串里。

Dim tcpClient As New _
   Net.Sockets.TcpClient("www.microsoft.com", 80)
Dim networkStream As _
   System.Net.Sockets.NetworkStream = _
   tcpClient.GetStream()
Dim sendBytes As [Byte]() = _
   System.Text.Encoding.ASCII.GetBytes( _
   "GET /default.htm" + ControlChars.CrLf)
networkStream.Write(sendBytes, 0, _
   sendBytes.Length)
' Reads the NetworkStream into a byte buffer.
Dim bytes(tcpClient.ReceiveBufferSize) As Byte
networkStream.Read(bytes, 0, _
   CInt(tcpClient.ReceiveBufferSize))
' Returns the data received from the host to the 
' console.
Dim returndata As String = _
   Encoding.ASCII.GetString(bytes)

 

VB.NET / 在Snap中進行序列化和反序列化

列表2.以下代碼在客戶端和服務器間建立了一個TCP連接。該服務器建立一個類,初始化它的state,對其進行序列化,然后通過網絡發送回客戶端??蛻舳藢λM行反序列化,并將該類的state堆存到調試器中。

' Server
Dim s As TcpListener
s = New TcpListener(8080)
s.Start()
Dim tcpClient As TcpClient = s.AcceptTcpClient()
Dim ns As NetworkStream = tcpClient.GetStream()
Dim bsw As New BufferedStream(ns, 2048)
Dim a As New Class1()
a.DateOfBirth = New Date(2000, 12, 12)
a.Name = "john"
Dim fm As New _
   System.Runtime.Serialization.Formatters. _
   Binary.BinaryFormatter()
fm.Serialize(bsw, a)
bsw.Flush()
bsw.Close()

' Client
Dim clientsocket As New TcpClient("localhost", _
   8080)
Dim ns As NetworkStream = clientsocket.GetStream()
Dim bfstreamr As New BufferedStream(ns, 2048)
System.Threading.Thread.CurrentThread.Sleep(2000)
Dim fm As New _
   System.Runtime.Serialization.Formatters. _
   Binary.BinaryFormatter()
Dim a As Class1 = CType( _
   fm.Deserialize(bfstreamr), Class1)
Debug.WriteLine(a.Name)
Debug.WriteLine(a.DateOfBirth)

描述
System.IO.StreamWriter 將特定書寫操作執行到底層設備中。
System.IO.StringWriter 提供針對字符串的類似于stream的操作。
System.Web.HttpWriter 建立一個作為儲備庫的streams。
System.Net.Sockets.NetworkStream 由Response.Output返回。將字符寫入HTTP響應中。
System.Web.UI.HtmlTextWriter 用于方便寫入HTML文檔。
System.CodeDom.Compiler.IndentedTextWriter 提供插入一個標簽字符串和跟蹤當前縮進層(indention level)的方法。

表2. BCL提供的五種TextWriters實現。

.NET Framework包含五種Stream抽象類的實現。StreamWriter類是目前最有用和最常見的。HttpWriter和HtmlTextWriter類是特別用于封裝NetworkStream的實現。

VB.NET / 有效加載XML文檔

列表3.在第一段代碼中,NetworkStream對象被傳入XmlTextReader。在第二段中,NetworkStream被傳入Xmldocument的Load方法中。這兩段代碼都是簡潔而有效的,因為它們都沒有生成不必要的臨時字符串。

'Load an XmlTextReader with the received data
Dim tcpClient As New _
   Net.Sockets.TcpClient("localhost", 80)
Dim ns As System.Net.Sockets.NetworkStream = _
   tcpClient.GetStream()
Dim xmltextreder As System.Xml.XmlTextReader = _
   New System.Xml.XmlTextReader(ns)
While xmltextreder.Read()
Debug.WriteLine(xmltextreder.Name)
End While
'Load an XmlDocument with the received data
Dim tcpClient As New _
   Net.Sockets.TcpClient("localhost", 80)
Dim ns As System.Net.Sockets.NetworkStream = _
   tcpClient.GetStream()
Dim l_dom As New Xml.XmlDocument()
l_dom.Load(ns)


關于作者:
Enrico Sabbadin是一名軟件架構師和開發者。它是VB2theMax小組的成員,并為Francesco Balena's Code Architects提供培訓和咨詢。他在自己的網站中(www.sabbasoft.com)有一些對MTS、COM+、VB/COM,以及.NET Enterprise Services等常見問題的解答。你可以通過esabbadin@vb2themax.com和他聯系。

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97