16.8 Stream抽象类
至此,我们知道了许多获取FileStream、StreamReader、StreamWriter对象的方法,但是还没有使用这些对象从文件读或者向文件写入数据。为了理解怎样去做,首先需要了解“流”的概念。在I/O操作中,“流”代表了一定量的数据。无论使用什么设备(文件、网络连接和打印机等)存储或者显示字节,“流”都能提供一种通用的方式来和字节队列进行交互。
抽象类System.IO.Stream定义了许多成员来提供对存储媒介(比如基层的文件或内存地址)实现同步或异步交互的支持。图16-6显示了Stream的一些派生类型。

图16-6 派生自Stream的类型
注解 “流”的概念不仅仅局限在访问文件或者内存地址。.NET类库提供了“流”来访问网络和其他一些与流相关的抽象设备。
Stream把数据抽象表现为原始的字节流,因此,使用原始的Stream类型有点模糊。一些从Stream派生的类型支持寻址(指获取和调整当前在流中位置的过程)。要理解Stream类提供的功能,可以先看看表16-6列举的一些主要成员。
表16-6 抽象Stream成员
|
成 员 |
作 用 |
|
CanRead CanSeek CanWrite |
检测当前流是否支持读、寻址和写 |
|
Close() |
关闭当前流并释放与之关联的所有资源(如套接字和文件句柄) |
|
Flush() |
使用当前的缓冲状态更新基层的数据源或储存库。如果流不实现缓冲,这个方法什么都不做 |
|
Length |
返回流的长度,以字节来表示 |
|
Position |
检测在当前流中的位置 |
|
Read() ReadByte() |
从当前流读取字节序列(或一个字节),并将此流中的位置偏移读取的字节数 |
|
Seek() |
设置当前流中的位置 |
|
SetLength() |
设置当前流的长度 |
|
Write() WriteByte() |
向当前流中写入字节序列(或一个字节),并将此流中的当前位置偏移写入的字节数 |
使用FileStream
FileStream类以合适的方式为基于文件的流提供了抽象Stream成员的实现。这是一个相当原始的流,它只能读取或写入一个字节或者字节数组。其实,我们通常不需要直接和FileStream类型的成员交互,而是使用各种Stream包装类,它们能更方便地处理文本数据和.NET类型。为了说明,让我们体验一下FileStream类型的同步读写能力。
新建一个名为FileStreamApp的控制台应用程序。目标是把一段简单的文字信息写入一个新建的文件myMessage.dat。然而,因为FileStream只能处理原始字节,我们必须把System.String编码成相应的字节数组。幸好System.Text命名空间定义了一个Encoding类型,它提供了一些成员,来实现在字符串和字节数组之间的编码/解码操作(请查看.NET Framework 2.0 SDK文档了解Encoding类型的完整细节)。
编码完成后,使用FileStream.Write()方法把字节数组保存到文件内。如果要把这些字节读回内存,还需要(通过Position属性)重置流内部的位置,然后调用ReadByte()方法。最后,可以在控制台上显示这些原始字节或者解码过的字符串。下面是完整的Main()方法:
// 不要忘记“使用”System.Text。
static void Main(string[] args)
{
Console.WriteLine("***** Fun with FileStreams *****\n");
// 获取一个FileStream对象。
FileStream fStream = File.Open(@"C:\myMessage.dat",
FileMode.Create);
// 把字符串编码成字节数组。
string msg = "Hello!";
byte[] msgAsByteArray = Encoding.Default.GetBytes(msg);
// 把byte[]写入文件。
fStream.Write(msgAsByteArray, 0, msgAsByteArray.Length);
// 重置流内部的位置。
fStream.Position = 0;
// 从文件读取字节并显示在控制台。
Console.Write("Your message as an array of bytes: ");
byte[] bytesFromFile = new byte[msgAsByteArray.Length];
for (int i = 0; i < msgAsByteArray.Length; i++)
{
bytesFromFile[i] = (byte)fStream.ReadByte();
Console.Write(bytesFromFile[i]);
}
// 显示解码后的字符串。
Console.Write("\nDecoded Message: ");
Console.WriteLine(Encoding.Default.GetString(bytesFromFile));
// 关闭流对象。
fStream.Close();
}
然而,这个例子在演示了把数据填充进文件的同时,也体现出了直接使用FileStream类型的缺点:需要操作原始字节。其他从Stream派生的类型也差不多,比如,如果想向一段内存写入字节队列的话,可以使用MemoryStream。同样,如果想向网络连接压入字节数组的话,可以使用NetworkStream类型。
幸好,System.IO命名空间提供了一些“读取器”和“编写器”类型来封装从Stream派生的类型的一些细节。
源代码 FileStreamApp项目的源代码位于Chapter 16子目录下。






