当你第一次用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()