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 | 指定操作系统应打开现有文件。文件一旦打开,就将被截断为为零字节大小。 |
灵活多样的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: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()