现在,处理文件有更强大的选项:流。流的概念已经存在很长时间了。流是一个用于传输数据的对象,数据可以向两个方向传输:
- 如果数据从外部源传输到程序中,这就是读取流。
- 如果数据从程序传输到外部源中,这就是写入流。
外部源常常是一个文件,但也不完全都是文件。它还可能是:
- 使用一些网络协议读写网络上的数据,其目的是选择数据,或从另一个计算机上发送数据。
- 读写到命名管道上
- 把数据读写到一个内存区域上。
一些流只允许写入,一些流只允许读取,一些流允许随机存取。随机存取允许在流中随机定位游标,例如,从流的开头开始读取,以后移动到流的末尾,再从流的一个中间位置继续读取。
在这些示例中,微软公司提供了一个.NET类System.IO.MemoryStream对象来读写内存,而System.Net.Sockets.NetworkStream对象处理网络数据。Stream类对外部数据源不做任何假定,外部数据源可以是文件流、内存流、网络流或任意数据源。
一些流也可以链接起来。例如,可以使用DeflateStream压缩数据。这个流可以写入FileStream、MemoryStream或NetworkStream。CryptoStream可以加密数据。也可以链接DeflateStream和CryptoStream,在写入FileStream。
使用流时,外部源甚至可以是代码中的一个变量。这听起来很荒缪,但使用流在变量之间传输数据的技术是一个非常有用的技巧,可以在数据类型之间转换数据。C语言使用类似的函数sprintf()在整型和字符串之间转换数据类型,或者格式化字符串。
使用一个独立的对象来传输数据,比使用FileInfo或DirectoryInfo类更好,因为把传输数据的概念与特定数据源分离开来,可以更容易交换数据源。流对象本身包含许多通用代码,可以在外部数据源和代码中的变量之间移动数据,把这些代码与特定数据源的概念分离开来,就更容易实现不同环境下代码的重用。
虽然直接读写流不是那么容易,但可以使用阅读器和写入器。这时另一个关于点分离。 阅读器和写入器可以读写流。例如,StringReader和StringWriter类,与本章后面用于读写文本文件的两个类StreamReader和StreamWriter一样,都是同一继承树的一部分,这些类几乎一定在后台共享许多代码。在System.IO名称空间中,与流相关的类的层次结构如下图所示。
对于文件的读写,最常用的类如下:
- FileStream(文件流)——这个类主要用于在二进制文件中读写二进制数据。
- StreamReader(流读取器)和StreamWriter(流写入器)——这两个类专门用于读写文本格式的流产品API。
- BinaryReader和BinaryWriter——这两个类专门用于读写二进制格式的流产品API。
使用这些类和直接使用底层的流对象之间的区别是,基本流是按照字节来工作的。例如,在保存某个文档时,需要把类型为long的变量的内容写入一个二进制文件中,每个long型变量都占用8个字节,如果使用一般的二进制流,就必须显示地写入内存的8个字节中。
在C#代码中,必须执行一些按位操作,从long值中提取这8个字节。使用BinaryWriter实例,可以把整个操作封装在BinaryWriter.Write()方法的一个重载方法中,该方法的参数是long型,它把8个字节写入流中(如果流指向一个文件,就写入该文件)。对象的BinaryReader.Reader()方法则从流中提取8个字节,恢复long的值。
1. 使用文件流
下面对流进行编程,以读写文件。FileStream实例用于读写文件中的数据。要构造FileStream实例,需要以下4条信息:
- 要访问的文件。
- 表示如何打开文件的模式——例如,新建一个文件或打开一个现有的文件。如果打开一个现有的文件,写入操作是覆盖文件原来的内容,还是追加到文件的末尾?
- 表示访问文件的方式——是只读、只写还是读写?
- 共享访问——表示是否独占访问文件。如果允许其他流同时访问文件,则这些流是只读、只写还是读写文件?
第一条信息通常用一个包含文件的完整路径名的字符串来表示,本章只考虑需要该字符串的那些构造函数。除了这些构造函数外,一些其他的构造函数用本地Wndows句柄来处理文件。其余3条信息分别由3个.NET枚举FileMode、FileAccess和FileShare来表示,这些枚举的值很容易理解,如下表所示。
注意,对于FileMode,如果要求的模式与文件的现有状态不一致,就会抛出一个异常。如果文件不存在,Append、Open和Truncate就会抛出一个异常;如果文件存在,CreateNew就会抛出一个异常。Create和OpenOrCreate可以处理这两种情况,但Create会删除任何现有的文件,新建一个空文件。因为FileAccess和FileShare枚举是按位标志,所以这些值可以与C#的按位OR运算符"|"合并使用。
(1). 创建FileStream
StreamSamples的示例代码使用如下名称空间:
System
System.Collectoins.Generic
System.Globalization
System.IO
System.Linq
System.Text
System.Threading.Tasks
FileStream有很多构造函数。下面的