C#IO流详解

 

 

文件流和数据流
不同的流可能有不同的存储介质,比如磁盘、内存等。.NET类库中定义了一个抽象类
Stream,表示对所有流的抽象,而每种具体的存储介质都可以通过Stream的派生类来实现
自己的流操作。
FileStream是对文件流的具体实现。通过它可以以字节方式对流进行读写,这种方式是
面向结构的,控制能力较强,但使用起来稍显麻烦。
此外,System.IO命名空间中提供了不同的读写器来对流中的数据进行操作,这些类通
常成对出现,一个用于读、另一个用于写。例如,TextReader和TextWriter以文本方式(即
ASCII方式)对流进行读写;而BinaryReader和BinaryWriter采用的则是二进制方式。
TextReader和TextWriter都是抽象类,它们各有两个派生类:StreamReader、StringReader以
及StreamWriter、StringWriter。


17.3.1 抽象类Stream
Stream支持同步和异步的数据读写。它和它的派生类共同组成了.NET Framework上IO
操作的抽象视图,这使得开发人员不必去了解IO操作的细节,就能够以统一的方式处理不
同介质上的流对象。
Stream类提供的公有属性见表17.4所示。
表17.4 Stream类的公有属性
属性名 类型 含义
CanRead bool 是否可以读取流中的数据
CanWrite bool 是否可以修改流中的数据
CanSeek bool 是否可以在流中进行定位
CanTimeout bool 流是否支持超时机制
Length long 流的长度
Position long 流的当前位置
ReadTimeout int 读超时限制
WriteTimeout int 写超时限制
其中前4个布尔类型的属性都是只读的。也就是说,一旦建立了一个流对象之后,流的
这些特性就不能被修改了。由于流是以序列的方式对数据进行操作,因而支持长度和当前
位置的概念。在同步操作中,一个流对象只有一个当前位置,不同的
程序或进程都在当前
位置进行操作;而在异步操作中,不同的程序或进程可以在不同位置上进行操作,当然这
需要文件的共享支持。最后,流的超时机制是指在指定的时间限制内没有对流进行读或写
操作,当前流对象将自动失效。
Stream类提供的公有方法则用于流的各项基本操作,请参看表17.5。
表17.5 Stream类的公有方法
方法标识 返回类型 用途
Read(byte[], int, int) int 从流中读取一个字节序列
Write(byte[], int, int) void 向流中写入一个字节序列
ReadByte() int 从流中读取一个字节
WriteByte(byte) void 向流中写入一个字节
Seek(long, SeekOrigin) long 设置流的当前位置
SetLength(long) void 设置流的长度
Flush() void 强制清空流的所有缓冲区
(续表)
方法标识 返回类型 用途
Close() void 关闭流
BeginRead(byte[], int, int, AsyncCallBack) IAsyncResult 开始流对象的异步读取
EndRead(IAsyncResult) int 结束流对象的异步读取
IAsyncResult BeginWrite(byte[], int, int,
AsyncCallBack, object)
IAsyncResult 开始流对象的异步写入
EndWrite(IAsyncResult) void 结束流对象的异步写入
在不同的情况下,Stream的派生类可能只支持这些成员的部分实现。例如,网络流一
般不支持位置的概念,系统也可能禁止对缓冲区的使用。
新建一个流时,当前位置位于流的开始,即属性Position的值为0。每次对流进行读写,
都将改变流的当前位置。可以将流的当前位置理解成“光标”的概念,它类似于字处理软
件中的光标。读操作从流的当前位置开始进行,读入指定的字节数,光标就向后移动对应
的字节数。写操作也是从流的当前位置开始进行,写入指定的字节数,光标然后停留在写
完的地方。
根据需要,可以使用Position属性或Seek方法来改变流的当前位置。不过Position属性指
的都是流的绝对位置,即从流的起始位置开始计算。该值为0时表示在起始位置,等于Length
的值减1时表示在结束位置。Seek方法则需要通过SeekOrigin枚举类型来指定偏移基准,即
是从开始位置、结束位置还是当前位置进行偏移。如果指定为SeekOrigin.End,那么偏移量
就应该为负数,表示将当前位置向前移动。看下面的代码:
//打开流,当前位置为0
Stream s = File.Open("C://bootlog.txt", FileMode.Open, FileAccess.Read);
//将当前位置移动到5
s.Seek(5, SeekOrigin.Begin);
//读取1个字节后,当前位置移动到6
s.ReadByte();
//读取10个字节后,当前位置移动到16
s.Read(new byte[20], 6, 10);
//将当前位置向前移动3个单位,移动到13
s.Seek(-3, SeekOrigin.Current);
//关闭流
s.Close();
如果指定的读写操作位置超出了流的有效范围,将引发一个EndOfStreamException异常。

 


17.3.2 文件流FileStream
作为文件流,FileStream支持同步和异步文件读写,也能够对输入输出进行缓存以提高
性能。
FileStream类提供了多达14个构造函数,能够以多种方式来构造FileStream对象,并在
构造的同时指定文件流的多个属性。当然,其中有一些构造函数是为了兼容旧版本的程序
而保留的。对于文件的来源,可以使用文件路径名,也可以使用文件句柄来指定。以文件
路径名为例,构造FileStream对象时至少需要指定文件的名称和打开方式两个参数,其他参
数如文件的访问权限、共享设置以及使用的缓存区大小等,则是可选的;如不指定则使用
系统的默认值,如默认访问权限为FileAccess.ReadWrite,共享设置为FileShare.Read。
下面的代码以只读方式打开一个现有文件,并且在关闭文件之前禁止任何形式的共享。
如果文件不存在,将引发一个FileNotFoundException:
FileStream fs = new FileStream("c://MyFile.txt", FileMode.Open,
FileAccess.Read, FileShare.None);
fs.Close();
除了使用FileStream的构造函数,也可以使用File的静态方法来获得文件流对象。File
类的静态方法Open和FileStream构造函数的参数类型基本一致,使用效果相同。例如上面的
代码等价于:
FileStream fs = File.Open("c://MyFile.txt", FileMode.Open, FileAccess.Read,
FileShare.None);
fs.Close();
File类的静态方法OpenRead和OpenWrite也能够返回一个FileStream对象,但它们只接受
文件名这一个参数。对于OpenRead方法,文件的打开方式为FileMode.Open,共享设置为
FileShare.Read,访问权限为 FileAccess.Read;而对于 OpenWrite方法,打开方式为
FileMode.OpenOrCreate,共享设置为FileShare.None,访问权限为FileAccess.Write。下面两
行代码是等价的:
FileStream fs = new FileStream("c://MyFile.txt", FileMode.OpenOrCreate,
FileAccess.Write, FileShare.None);
FileStream fs = File.OpenWrite("c://MyFile.txt");
FileStream类的ReadByte和WriteByte方法都只能用于单字节操作。要一次处理一个字节
序列,需要使用Read和Write方法,而且读写的字节序列都位于一个byte数组类型的参数中。
看下面的程序:
//程序清单P17_4.cs:using System;
using System.IO;
namespace P17_4
{
class FileStreamSamle
{
static void Main()
{
//创建一个文件流
FileStream fs = new FileStream("c://MyFile.txt",
FileMode.Create);
//将字符串的内容放入缓冲区
string str = "Welcome to the Garden!";
byte[] buffer = new byte[str.Length];
for (int i = 0; i < str.Length; i++)
{
buffer[i] = (byte)str[i];
}
//写入文件流
fs.Write(buffer, 0, buffer.Length);
string msg = "";
//定位到流的开始位置
fs.Seek(0, SeekOrigin.Begin);
//读取流中前7个字符
for (int i = 0; i < 7; i++)
{
msg += (char)fs.ReadByte();
}
//显示读取的信息和流的长度
Console.WriteLine("读取内容为:{0}", msg);
Console.WriteLine("文件长度为:{0}", fs.Length);
//关闭文件流
fs.Close();
}
}
}
程序的输出为:
读取内容为:Welcome
文件长度为:22
最后还是要提醒一句,使用完FileStream对象后,一定不能忘记使用Close方法关闭文
件流,否则不仅会使别的程序不能访问该文件,还可能导致文件损坏。

 


17.3.3 流的文本读写器
StreamReader和StreamWriter主要用于以文本方式对流进行读写操作,它们以字节流为
操作对象,并支持不同的编码格式。
StreamReader和StreamWriter通常成对使用,它们的构造函数形式也一一对应。可以通
过指定文件名或指定另一个流对象来创建StreamReader和StreamWriter对象。如有必要,还
可以指定文本的字符编码、是否在文件头查找字节顺序标记,以及使用的缓存区大小。
文本的字符编码默认为UTF-8格式。在命名空间System.Text中定义的Encoding类对字符
编码进行了抽象,它的5个静态属性分别代表了5种编码格式:
• ASCII
• Default
• Unicode
• UTF-7
• UTF-8
不过, Encoding类的Default属性表示系统的编码,默认为ANSI代码页,这和
StreamReader和 StreamWriter中默认的 UTF-8编码是不一样的。通过 StreamReader和
StreamWriter类的公有属性Encoding可以获得当前使用的字符编码。StreamReader类还有一
个布尔类型的公有属性EndOfStream,用于指示读取的位置是否已经到达流的末尾。
下面的代码从一个文件流构造了一个 StreamReader对象和 StreamWriter对象,还为
StreamWriter对象指定了Unicode字符编码。不过在实际应用中,为同一文件进行读写操作
所构造的两个对象通常使用同样的字符编码格式:
FileStream fs = new FileStream("c://MyFile.txt", FileMode.Create);
StreamReader sr = new StreamReader(fs);
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.Unicode);
sw.Close();
sr.Close();
fs.Close();
注意在关闭文件时,要先关闭读写器对象,再关闭文件流对象。如果对同一个文件同
时创建了 StreamReader和 StreamWriter对象,则应先关闭 StreamWriter对象,再关闭
StreamReader对象。否则将引发ObjectDisposedException异常。
即使是直接使用文件名来构造StreamReader或StreamWriter对象,或是使用File类的静
态方法OpenText和AppendText来创建StreamReader或StreamWriter对象,过程当中系统都会
自动生成隐含的文件流,读写器对文件的读写还是通过流对象进行的。该文件流对象可以
通过StreamReader或StreamWriter对象的BaseStream属性获得。
不通过文件流而直接创建StreamReader对象时,默认的文件流对象是只读的。以同样
的方式来创建StreamWriter对象的话,默认的文件流对象是只写的。下面的程序说明了这一
点:
//程序清单P17_5.cs:
using System;
using System.IO;
namespace P17_5
{
class BaseStreamSample
{
static void Main()
{
StreamReader sr = new StreamReader("c://MyFile.txt");
Console.WriteLine("CanRead:{0}", sr.BaseStream.CanRead);
Console.WriteLine("CanWrite:{0}", sr.BaseStream.CanWrite);
sr.Close();
StreamWriter sw = new StreamWriter("c://MyFile.txt");
Console.WriteLine("CanRead:{0}", sw.BaseStream.CanRead);
Console.WriteLine("CanWrite:{0}", sw.BaseStream.CanWrite);
sw.Close();
}
}
}
程序P17_5.cs的输出为:
CanRead:True
CanWrite:False
CanRead:False
CanWrite:True
由于使用的是不同的流对象,此时就不能同时使用StreamReader和StreamWriter对象来
打开同一个文件。在程序P17_5.cs的代码中,如果不关闭StreamReader对象就创建
StreamWriter对象,将引发一个 IOException异常。使用 File类的静态方法OpenText和
AppendText时,情况也一样。
StreamReader中可以使用4种方法对流进行读操作:
• Read,该方法有两种重载形式,在不接受任何输入参数时,它读取流的下一个字符;
当在参数中指定了数组缓冲区、开始位置和偏移量时,它读入指定长度的字符数组。
• ReadBlock,从当前流中读取最大数量的字符,并将数据输出到缓冲区。
• ReadLine,从当前流中读取一行字符,即一个字符串。
• ReadToEnd,从流的当前位置开始,一直读取到流的末尾,并把所有读入的内容都
作为一个字符串返回;如果当前位置位于流的末尾,则返回空字符串。
StreamReader最常用的是ReadLine方法,该方法一次读取一行字符。这里“行”的定义
是指一个字符序列,该序列要么以换行符(“/n”)结尾,要么以换行回车符(“/r/n”)
结尾。
StreamWriter则提供了Write和WriteLine方法对流进行写操作。不过这两个方法可以接
受的参数类型则丰富得多,包括char、int、string、float、double乃至object等,甚至可以对
字符串进行格式化。看下面这段代码:
//创建一个文件流
FileStream fs = new FileStream("c://MyFile.txt", FileMode.Create,
FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine(25); //写入整数
sw.WriteLine(0.5f); //写入单精度浮点数
sw.WriteLine(3.1415926); //写入双精度浮点数
sw.WriteLine(’A’); //写入字符
sw.Write ("写入时间:"); //写入字符串
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second = DateTime.Now.Second;
//写入格式化字符串
sw.WriteLine("{0}时{1}分{2}秒", hour, minute, second);
//关闭文件
sw.Close();
fs.Close();
得到的文本文件内容是:
25
0.5
3.1415926
A
写入时间:10时11分9秒
Write和 WriteLine方法的使用读者应该很熟悉,因为它们所提供的重载形式和
Console.Write以及Console.WriteLine方法完全一样。这些重载方法只是为了使用方便,实际
368 C# 2.0 程序设计教程
上写入任何类型的对象时,都调用了对象的ToString方法,然后将字符串写入流中。不同的
是,WriteLine方法在每个字符串后面加上了换行符,而Write方法则没有。
StringReader和StringWriter同样是以文本方式对流进行IO操作,但它们以字符串为操作
对象,功能相对简单,而且只支持默认的编码方式。

 


17.3.4 流的二进制读写器
BinaryReader和BinaryWriter以二进制方式对流进行IO操作。它们的构造函数中需要指
定一个Stream类型的参数,如有必要还可以指定字符的编码格式。和文本读写器不同的是,
BinaryReader和BinaryWriter对象不支持从文件名直接进行构造。
类似的,可以通过BinaryReader和BinaryWriter对象的BaseStream属性来获得当前操作
的流对象。
BinaryReader类提供了多个读操作方法,用于读入不同类型的数据对象,这些方法请参
见表17.6。
表17.6 BinaryReader类的读操作方法
方法标识 返回类型 用途
Read(byte[],int, int) int 指定位置和偏移量,从流中读取一组字节到缓冲区
ReadBoolean() bool 从流中读取一个布尔值
ReadByte() byte 从流中读取一个字节
ReadBytes() byte[] 从流中读取一个字节数组
ReadChar() char 从流中读取一个字符
ReadChars() char[] 从流中读取一个字符数组
ReadDecimal() decimal 从流中读取一个十进制数值
ReadDouble() double 从流中读取一个双精度浮点型数值
ReadInt16() short 从流中读取一个短整型整数值
ReadInt32() int 从流中读取一个整数值
ReadInt64() long 从流中读取一个长整型整数值
ReadSByte() sbyte 从流中读取一个有符号字节
ReadSingle() float 从流中读取一个单精度浮点型数值
ReadString() string 从流中读取一个字符串
ReadUInt16() ushort 从流中读取一个无符号短整型整数值
ReadUInt32() uint 从流中读取一个无符号整数值
ReadUInt64() ulong 从流中读取一个无符号长整型整数值
使用这些方法时,注意,方法名称中指代的都是数据类型在System空间的原型。例如
读取单精度浮点型数值,方法名称是ReadSingle而不是ReadFloat,另外读取short、int、long
类型的整数值,方法名称也分别是ReadInt16、ReadInt32和ReadInt64。
而BinaryWriter则只提供了一个方法Write进行写操作,但提供了多种重载形式,用于写入不同类型的数据对象。各种重载形式中的参数类型和个数与StreamWriter中基本相同。
下面的代码演示了使用BinaryReader和BinaryWriter对象进行对应的读写操作:
//创建文件流和二进制读写器对象
FileStream fs = new FileStream("c://MyFile.bin", FileMode.OpenOrCreate);
BinaryWriter bw = new BinaryWriter(fs);
BinaryReader br = new BinaryReader(fs);
//依次写入各类型数据
bw.Write(25);
bw.Write(0.5f);
bw.Write(3.1415926);
bw.Write(’A’);
bw.Write("写入时间:");
bw.Write(DateTime.Now.ToString());
//定位到流的开始位置
fs.Seek(0, SeekOrigin.Begin);
//依次读出各类型数据
int i = br.ReadInt32();
float f = br.ReadSingle();
double d = br.ReadDouble();
char c = br.ReadChar();
string s = br.ReadString();
DateTime dt = DateTime.Parse(br.ReadString());
//关闭文件
bw.Close();
br.Close();
fs.Close();

 

 

 


17.3.5 常用的其他流对象
除了FileStream类之外,代表具体流的、Stream类的常用派生类还有:
• MemoryStream,表示内存流,支持内存文件的概念,不需要使用缓冲区;
• UnmanagedMemoryStream,和MemoryStream类似,但支持从可控代码访问不可控
的内存文件内容;
• NetworkStream,表示网络流,通过网络套接字发送和接收数据,支持同步和异步
访问,但不支持随机访问;
• BufferStream,表示缓存流,为另一个流对象维护一个缓冲区;
• GZipStream,表示压缩流,支持对数据流的压缩和解压缩;
• CryptoStream,表示加密流,支持对数据流的加密和解密。
同样,可以由这些流对象构造出文本读写器或二进制读写器,并进行相应方式的读写
操作。
程序P17_6.cs演示了利用文件流和缓存流来共同维护一个三角函数表:
//程序清单P17_6.cs:
using System;
using System.IO;
namespace P17_6
{
class BufferedStreamSample
{
static void Main()
{
TriangleTable table = new TriangleTable();
Console.WriteLine("请输入度数(0~179之间):");
try
{
int x = int.Parse(Console.ReadLine());
Console.WriteLine("请选择函数类型:");
Console.WriteLine("0.正弦函数 1.余弦函数 2.正切函数 3.余切函数
");
int iType = int.Parse(Console.ReadLine());
table.Open();
double y = table.GetFunction(x, iType);
Console.WriteLine("函数值 = {0}", y);
}
catch (Exception)
{
File.Delete("C://Triangle.tbl");
}
finally
{
table.Close();
}
}
}

public delegate double TwoIntFunction(int param1, int param2);

///
/// 类TriangleTable:三角函数表
///
public class TriangleTable{
private FileStream m_baseStream;
private BufferedStream m_stream;
private BinaryReader m_reader;
public TwoIntFunction GetFunction;

 

public TriangleTable()
{
if (!File.Exists("C://Triangle.tbl"))
{
m_baseStream = new FileStream("C://Triangle.tbl",
FileMode.Create);
BinaryWriter writer = new BinaryWriter(m_baseStream);
byte[] buf = new byte[8];
for (int i = 0; i < 180; i++)
{
double sin = Math.Sin(Math.PI * i / 180);
double cos = Math.Sqrt(1 - sin * sin);
double tan = sin / cos;
double ctan = cos / sin;
writer.Write(sin);
writer.Write(cos);
writer.Write(tan);
writer.Write(ctan);
}
writer.Close();
m_baseStream.Close();
}
}

public void Open()
{
m_baseStream = new FileStream("C://Triangle.tbl",
FileMode.Open);
m_stream = new BufferedStream(m_baseStream);
m_reader = new BinaryReader(m_stream);
GetFunction = delegate(int angle, int iType)
{
if (iType < 0 || iType > 3)
throw new ArgumentOutOfRangeException("参数应为0~3之间的
整数");
m_stream.Seek(sizeof(double) * (4 * angle + iType),
SeekOrigin.Begin);
return m_reader.ReadDouble();
};
}

public void Close()
{
m_reader.Close();
m_baseStream.Close();
m_stream.Close();
}
}
}
在三角函数表类TriangleTable中定义了 3个私有字段,类型分别为FileStream、
BufferedStream和BinaryReader。首次使用该类时,计算出三角函数表所维护的全部数据,
并存放在一个文件中。以后每次使用该类,都将数据读至缓存流,并通过缓存流直接获取
函数值。读取函数值的功能由代表字段GetFunction实现,注意它在缓存流中的定位方式。
由于缓存流中顺序存放了0~180之间每个角度的4个三角函数值,因此度数为angle的三角
函数值的存放位置是从流的开始位置偏移(4 * angle)个double数据类型所占的字节数,并
且从该位置起依次存放的是正弦、余弦、正切和余切函数值。

 

原文地址:http://www.wolaikanshu.cn/Article/Programming/C/201002/39462_2.html

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值