+-
C#(Sharp)进阶篇:文件操作

文章目录

8.1 文件系统概述 8.2 驱动器、目录和文件 8.2.1 与IO操作相关的枚举 8.2.2 驱动器 8.2.3 目录 8.2.4 文件 8.3 文件流和数据流 8.3.1 抽象类Stream 8.3.2 文件流FileStream 8.3.3 流的文本读写器 8.3.4 流的二进制读写器 8.3.5 常用的其他流对象 8.4 应用实例

8.1 文件系统概述

文件系统是操作系统的一个重要组成部分。文件系统所要解决的问题包括管理存储设备,决定文件的存放位置和方式,提供共享能力,保证文件安全性,提供友好的用户接口等。

C#将文件视为一个字节序列,以流的方式对文件进行操作。流是字节序列的抽象概念,文件、输入/输出设备、内部进程通信管道以及

TCP/IP 套接字等都可以视为一个流。和磁盘文件直接相关的流叫做文件流。

文件中的数据可以有不同的编码格式,最根本的两种是

ASCII编码和二进制编码。

整数1000的存储:

.NET类库 20000 的

System.IO命名空间中提供了完整的对文件和流的访问支持。

8.2 驱动器、目录和文件

.NET类库中提供了

DriveInfo类、 Directory类和 File类,分别对驱动器、目录和文件进行了封装。

这3个类都是密封类,无法从中派生出其他类;而且,

Directory和 File类属于抽象类,无法创建它们的实例,而只能通过类的原型调用其公有的静态方法成员。

8.2.1 与IO操作相关的枚举

1.FileAccess

该枚举类型表示对文件的访问权限,可以是以下值:

Read:对文件拥有读权限; ReadWrite:对文件同时拥有读写权限; Write:对文件拥有写权限。

2.FileAttributes

该枚举类型表示文件的类型,可以是以下值:

Archive:存档文件 Compressed:压缩文件 Device:设备文件 Directory:目录 Encrypted:加密文件 Hidden:隐藏文件 Normal:普通文件 NotContentIndexed:无索引文件 Offline:脱机文件 ReadOnly:只读文件 ReparsePoint:重分析文件 SparseFile:稀疏文件 System:系统文件 Temporary:临时文件

3.FileMode
该枚举类型表示文件的打开方式:

Append:以追加方式打开文件,如果文件存在则到达文件末尾,否则创建一个新文件;
Create:创建并打开一个新文件,如果文件已经存在则覆盖旧文件;
CreateNew:创建并打开一个新文件,如果文件已经存在发生异常;
Open:打开现有文件,如果文件不存在发生异常;
OpenOrCreate:打开或新建一个文件,如果文件已经存在则打开它,否则创建并打开一个新文件;
Truncate:打开现有文件,并清空文件内容。

4.FileShare

该枚举类型表示文件的共享方式,可以是以下值:

None:禁止任何形式的共享;
Read:读共享,打开文件后允许其他进程对文件进行读操作;
ReadWrite:读写共享,打开文件后允许其他进程对文件进行读和写操作;
Write:写共享,打开文件后允许其他进程对文件进行写操作。

5.SeekOrigin

该枚举类型表示以什么为基准来表示文件流中的偏移量,可以是以下值:

Begin——从文件流的起始位置计;
Current:从文件流的当前位置计;
End——从文件流的结束位置计。

6.NotifyFilters

该枚举类型用于指定对文件或目录中哪些属性的修改进行监视,可以是以下值:

Attributes:对属性的变化进行监视;
CreationTime:对创建时间的变化进行监视;
DirectoryName:对目录名称的变化进行监视;
FileName:对文件名称的变化进行监视;
LastAccess:对最后一次访问时间的变化进行监视;
LastWrite:对最后一次写入时间的变化进行监视;
Security:对安全性设置的变化进行监视;
Size:对文件大小的变化进行监视。

7.DriveType

该枚举类型用于定义与驱动器类型有关的常量,可以是以下值:

CDRom:CD-ROM驱动器;
Fixed:固定磁盘驱动器;
NetWork——网络驱动器;
NoRootDirectory:不含根目录的驱动器;
Ram:RAM闪盘驱动器;
Removable:可移动存储设备;
Unknown:驱动器设备类型未知。

8.2.2 驱动器

DriveInfo类共有属性表

DriveInfo类还提供了一个公有的静态方法GetDrives,该方法返回一个DriveInfo[]类型的数组,表示当前计算机上所有逻辑驱动器的列表。如果该方法的调用者没有足够的权限,将引发一个UnauthorizedAccessException异常。

【例8-1】显示当前计算机上所有驱动器的相关信息。

8.2.3 目录

Directory类的公有静态方法

Directory类的基本用法

1.路径名的使用

(1)在使用目录和文件的路径名时,注意一定要使用转义符“\”来替代字符串中的字符“\”, 或者使用@取消转义,如@"

c:\MyDirectory"。

(2)在目录和文件操作中,可以使用全路径名,也可以使用部分路径名。如果使用的是部分路径名,则默认操作都在当前目录下进行。例如,对于下面的代码:

Directory.CreateDirectory("MyDirectory");

如果当前目录是C盘根目录,那么所创建目录的全路径名就是“C:\MyDirectory”;而如果当前目录是“C:\Windows”,那么所创建目录的全路径名就是“

C:\Windows\ MyDirectory”。

2.目录操作例

(1)在C盘根目录下创建了一个名为“MyDirectory”的目录,并将其移动到C盘Windows子目录下,最后删除该目录,代码为:

Directory.CreateDirectory("c:\\MyDirectory"); Directory.Move(@"c:\MyDirectory", @"c:\Windows\MyDirectory"); Directory.Delete(@"c:\Windows\MyDirectory");

(2)获取当前目录下的所有子目录,如读出c:\Dir1\目录下的所有子目录,并将其存储到字符串数组中:

string [] Directorys; Directorys = Directory. GetDirectories (@"c:\Dir1");

(3)获得所有逻辑盘符,如:

string[] AllDrivers=Directory.GetLogicalDrives();

(4)获取当前目录下的所有文件,如读出c:\Dir1\目录下的所有文件,并将其存储到字符串数组中。

string [] Files; Files = Directory. GetFiles (@"c:\Dir1",);

(5)可以使用Directory的GetCurrentDirectory和SetCurrentDirectory方法来获取和设置当前目录。

3.关于DirectoryInfo类

Directory类中一些静态方法的返回类型为DirectoryInfo类,它封装了目录的有关信息。DirectoryInfo类的功能有很多地方与Directory重叠,例如它使用Create和Delete方法来创建和删除目录,使用GetDirectories和GetFiles方法来获取子目录和文件列表,而且可以通过Name、Parent和Root等属性获取目录的名称、上层目录和根目录等信息。不过DirectoryInfo类不是抽象类,可以指定目录的路径名来创建一个DirectoryInfo对象,并通过对象来调用其方法和属性。

8.2.4 文件

File类的公有静态方法


File类的基本用法

1.文件打开方法:

File.Open

格式:

public static FileStream Open(string path, FileMode mode)

如:

FileStream fs = File.Open( @"c:\ Ex\e1.txt",FileMode.Append); byte [] Info={(byte)'c',(byte)'a',(byte)'t'}; fs.Write(Info,0,Info.Length); fs.Close();

2.文件创建方法:

File.Create

格式:

public static FileStream Create(string path)

例如:

FileStream fs = File.Create(@"c:\Ex\e1.txt"); fs.Close();

3.文件删除方法:

File.Delete

格式:

public static FileStream Delete(string path)

例如:

File.Delete(@"c:\Ex\e1.txt");

4.文件复制方法:

File.Copy

格式:

public static void Copy(string sourceFileName,string destFileName,bool overwrite)

例如:

File.Copy(@"c:\Ex\e1.txt",@"c:\Ex\e2.txt",true);

由于Cope方法的OverWrite参数设为true,所以如果e2.txt文件已存在的话,将会被复制过去的文件所覆盖。

5.文件移动方法:

File.Move

格式:

public static void Move(string sourceFileName,string destFileName)

例如:

File.Move(@"c:\Ex\BackUp.txt",@"c:\BackUp.txt");

注意:只能在同一个逻辑盘下进行文件转移。

6.设置文件属性方法:

File.SetAttributes

格式:

public static void SetAttributes(string path,FileAttributes fileAttributes)

例如:

File.SetAttributes(@"c:\Ex\e1.txt", FileAttributes.ReadOnly|FileAttributes.Hidden);

7.判断文件是否存在的方法:

File.Exist

格式:

public static bool Exists(string path)

例如:

if(File.Exists(@"c:\Ex\e1.txt")) //判断文件是否存在 {…}//处理代码

【例8-2】列表输出当前目录下所有文件的时间信息。

8.3 文件流和数据流

不同的流可能有不同的存储介质,比如磁盘、内存等。.NET类库中定义了一个抽象类Stream,表示对所有流的抽象,而每种具体的存储介质都可以通过Stream的派生类来实现自己的流操作。

FileStream是对文件流的具体实现。通过它可以以字节方式对流进行读写,这种方式是面向结构的,控制能力较强,但使用起来稍显麻烦。

System.IO命名空间中提供了不同的读写器来对流中的数据进行操作,这些类通常成对出现,一个用于读、另一个用于写。
例如,TextReader和TextWriter以文本方式(即ASCII方式)对流进行读写;

BinaryReader和BinaryWriter采用的则是二进制方式。

TextReader和TextWriter都是抽象类,它们各有两个派生类:

StreamReader、 StringReader以及 StreamWriter、 StringWriter。

8.3.1 抽象类Stream

Stream支持同步和异步的数据读写。它和它的派生类共同组成了.NET Framework上IO操作的抽象视图,这使得开发人员不必去了解IO操作的细节,就能够以统一的方式处理不同介质上的流对象。

Stream`类提供的公有属性

 Stream类的公有属性中的前4个布尔类型的属性都是只读的。也就是说,一旦建立了一个流对象之后,流的这些特性就不能被修改了。

 由于流是以序列的方式对数据进行操作,因而支持长度和当前位置的概念。在同步操作中,一个流对象只有一个当前位置,不同的程序或进程都在当前位置进行操作;而在异步操作中,不同的程序或进程可以在不同位置上进行操作,当然这需要文件的共享支持。

 最后,流的超时机制是指在指定的时间限制内没有对流进行读或写操作,当前流对象将自动失效。

Stream类提供的公有方法用于流的各项基本操作。

在不同的情况下,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异常。

8.3.2 文件流FileStream

FileStream是对文件流的具体实现。

FileStream类提供了多达14个构造函数,能够以多种方式来构造

FileStream对象,并在构造的同时指定文件流的多个属性。

对于文件的来源,可以使用文件路径名,也可以使用文件句柄来指定。以文件路径名为例,构造

FileStream对象时至少需要指定文件的名称和打开方式两个参数,其他参数如文件的访问权限、共享设置以及使用的缓存区大小等,则是可选的;如不指定则使用系统的默认值,如默认访问权限为 FileAccess.ReadWrite,共享设置为 FileShare.Read。

下面的代码以只读方式打开一个现有文件,并且在关闭文件之前禁止任何形式的共享。

FileStream fs = new FileStream(@“c:\MyFile.txt”, FileMode.Open, FileAccess.Read, FileShare.None); fs.Close();

如果文件不存在,将引发一个

FileNotFoundException。

除了使用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数组类型的参数中。

【例8-3】按字节把字符串写入文件

C:\MyFile.txt。

注意:使用完

FileStream对象后,一定不能忘记使用 Close()方法关闭文件流,否则不仅会使别的程序不能访问该文件,还可能导致文件损坏。

8.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:\Test.txt", FileMode.Create); StreamReader sr = new StreamReader(fs); StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.Unicode); sw.Close(); sr.Close(); fs.Close();

说明:(1)在实际应用中,为同一文件进行读写操作所构造的两个对象通常使用同样的字符编码格式。

sw.Close(); sr.Close(); fs.Close();

说明:(2)注意在关闭文件时,要先关闭读写器对象,再关闭文件流对象。如果对同一个文件同时创建了 StreamReader和 StreamWriter对象,则应先关闭 StreamWriter对象,再关闭StreamReader对象,否则将引发ObjectDisposedException异常。

 即使是直接使用文件名来构造StreamReader或StreamWriter对象,或是使用File类的静态方法OpenText和AppendText来创建StreamReader或StreamWriter对象,过程当中系统都会自动生成隐含的文件流,读写器对文件的读写还是通过流对象进行的。该文件流对象可以通过StreamReader或StreamWriter对象的BaseStream属性获得。

 不通过文件流而直接创建StreamReader对象时,默认的文件流对象是只读的。以同样的方式来创建StreamWriter对象的话,默认的文件流对象是只写的。

【例8-4】StreamReader和 StreamWriter的使用。

说明:
(1)例8-4中,由于使用的是不同的流对象,此时就不能同时使用StreamReader和StreamWriter对象来打开同一个文件。

(2)如果不关闭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();

执行上述代码,输出文本文件内容是:

关于Write 和Reader的说明:

(1)Write和 WriteLine方法的使用读者应该很熟悉,因为它们所提供的重载形式和Console.Write以及Console.WriteLine方法完全一样。这些重载方法只是为了使用方便,实际上写入任何类型的对象时,都调用了对象的ToString()方法,然后将字符串写入流中。不同的是,WriteLine方法在每个字符串后面加上了换行符,而Write方法则没有。

(2)StringReader和StringWriter同样是以文本方式对流进行IO操作,但它们以字符串为操作对象,功能相对简单,而且只支持默认的编码方式。

8.3.4 流的二进制读写器

 BinaryReader和BinaryWriter以二进制方式对流进行IO操作。它们的构造函数中需要指定一个Stream类型的参数,如有必要还可以指定字符的编码格式。和文本读写器不同的是,BinaryReader和BinaryWriter对象不支持从文件名直接进行构造。

 可以通过BinaryReader和BinaryWriter对象的BaseStream属性来获得当前操作的流对象。

 BinaryReader类提供了多个读操作方法,用于读入不同类型的数据对象。

//创建文件流和二进制读写器对象

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());

注意:

使用上述方法时,方法名称中指代的都是数据类型在System空间的原型。
(1)读取单精度浮点型数值,方法名称是ReadSingle而不是ReadFloat,另外读取short、int、long类型的整数值,方法名称也分别是ReadInt16、ReadInt32和ReadInt64。

(2)BinaryWriter则只提供了一个方法Write进行写操作,但提供了多种重载形式,用于写入不同类型的数据对象。各种重载形式中的参数类型和个数与StreamWriter中基本相同。

下列代码演示使用BinaryReader和BinaryWriter对象进行对应的读写操作:
//定位到流的开始位置

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();

8.3.5 常用的其他流对象

 除了FileStream类之外,代表具体流的、Stream类的常用派生类还有:

MemoryStream,表示内存流,支持内存文件的概念,不需要使用缓冲区; UnmanagedMemoryStream,和MemoryStream类似,但支持从可控代码访问不可控的内存文件内容; NetworkStream,表示网络流,通过网络套接字发送和接收数据,支持同步和异步访问,但不支持随机访问; BufferStream,表示缓存流,为另一个流对象维护一个缓冲区; GZipStream,表示压缩流,支持对数据流的压缩和解压缩; CryptoStream,表示加密流,支持对数据流的加密和解密。

8.4 应用实例

IO程序示例——文件加密器

(1)通过.NET类库中定义的CryptoStream类,以及File类的静态方法Encrypt和Decrypt,都能够实现对文件流的加密和解密操作,但相应的算法被封装在类的内部,开发人员无需了解算法的细节就可以实现这些功能。

(2)示例程序使用简单的异或算法来实现对文件流的加密,加密功能封装在自定义的FileStream类的派生类CzCryptStream中。

(3)异或加密算法的原理是:把数据码和密钥码进行二进制位的异或运算得到密文。由于把一个数同另一个数进行两次异或运算,结果还是原来的数,这就使得对明文进行两次加密就可以还原,也就是说异或法的加密密钥和解密密钥是相同的。

【例8-5】文件加密和解密。