C# 篇基础知识6——文件和流

计算机以文件的形式把数据存储在磁盘、光盘等存储设备上。文件的管理和操作是操作系统的一个重要组成部分,.NET 框架提供了一组功能强大的类,可以方便地对文件进行操作和管理。

1.文件操作相关的类

用于文件操作的类位于System.IO 命名空间中,用这些类可以方便地对文件进行创建、读写、复制、删除、移动、打开等操作。

2.File类和FileInfo类

命名空间 System.IO 中的File 类用于对文件进行创建、打开、复制、移动、删除、重命名等典型操作,并能获取或设置文件的属性信息。File 类中所有的方法都是静态的,使用起来非常简单,File 类的部分方法如下图所示:

File类的方法使用起来非常方便:

using System;

using System.IO;

static void Main(string[] args)

{  string path = @"D: est.txt";

if(File.Exists(path)) //如果文件已经存在,则读取文件内容

{  //读取文件

string contents = File.ReadAllText(path);

Console.WriteLine("读取文件: " + contents);  }

else //如果文件不存在,则创建文件并写入内容

{  string contents = "无可奈何花落去, 似曾相识燕归来, 小园香径独徘徊。";

File.WriteAllText(path, contents); //写入文件

Console.WriteLine("文件已写入。" );  }

}

注意 WriteAllText()、WriteAllLines()和WriteAllBytes()方法都会覆盖以前的文件,使用时要特别小心。要想在文件尾部追加新文本,请使用AppendAllText()方法。

FileInfo 类和File 类相似,可以创建、复制、移动、删除文件,可以获取或设置文件的属性,只是少了读写文件的功能。FileInfo 类的成员方法都是非静态的,使用方法前需要先创建一个FileInfo 类的对象,然后通过FileInfo 对象调用方法。当频繁操作某文件时,使用FileInfo 类的效率会更高。

(1)关于文件的异常

操作文件时经常会发生异常,比如指定文件不存在,路径无效,没有权限等。关于文件的异常的方法如图示,因此关于文件的操作一般要放在 try-catch 结构中,以处理可能发生的异常情况。

try

{  string path = @"D: est.txt";

//如果文件已经存在,则读取文件内容

if(File.Exists(path))

{  //读取文件

string contents = File.ReadAllText(path);

Console.WriteLine("读取文件: " + contents);

}

else //如果文件不存在,则创建文件并写入内容

{

string contents = "无可奈何花落去, 似曾相识燕归来, 小园香径独徘徊。";

//写入文件

File.WriteAllText(path, contents);

Console.WriteLine("文件已写入。" );

}

}

catch (Exception e)

{  //异常处理

Console.WriteLine(e.Message);

}

3.Directory类和DirectoryInfo类

System.IO 命名空间中的Directory 类用于执行对目录(文件夹)的操作,比如创建、移动、删除、重命名等,也可通过它获取或设置目录的属性。Directory 类的部分方法如图所示,所有 Directory 类中的方法都是静态的,使用起来非常方便。

using System;

using System.IO;

static void Main(string[] args)

{  try

{  string path = @"D:Program FilesWindows Media Player";

if (Directory.Exists(path)

{  //获取子目录

string[] dirs = Directory.GetDirectories(path);

Console.WriteLine("子目录:");

foreach (string dir in dirs) {

Console.WriteLine(dir); }

//获取文件

string[] files = Directory.GetFiles(path);

Console.WriteLine("文件:");

foreach (string file in files) {Console.WriteLine(file);}

}

else{Console.WriteLine("目录不存在");}

}

catch (Exception e)

{//异常处理Console.WriteLine(e.Message);}

}

操作结果所图所示,DirectoryInfo 类和Directory 类功能相似,区别是DirectoryInfo 类的成员方法都是非静态的,使用方法前需要先创建一个DirectoryInfo 类的对象。当频繁操作某目录时,使用DirectoryInfo 类的效率会更高。

4.path类

通过 System.IO 命名空间中的Path 类,我们可以方便的处理路径。Path 类的部分字段和方法如图所示。Path 类中的很多字段是和操作系统关联的,在不同的操作系统中可能有不同的结果。比如DirectorySeparatorChar,在Windows 和Macintosh 操作系统中的值是“”,在Unix 操作系统中为“/”;VolumeSeparatorChar 在Windows 和Macintosh 操作系统中为“:”,在Unix操作系统中为“/”;PathSeparator 在 Windows 操作系统中默认值是“;”,在 Unix 操作系统上为“:”。

using System.IO;

//输出分隔符

Console.WriteLine("DirectorySeparatorChar" + Path.DirectorySeparatorChar);

Console.WriteLine(" PathSeparator" + Path.PathSeparator);

Console.WriteLine(" VolumeSeparatorChar" + Path.VolumeSeparatorChar);

//输出路径信息

string path = @"D:Program FilesWindows Media Playerwmplayer.exe";

Console.WriteLine(" GetFileName" + Path.GetFileName(path));

Console.WriteLine(" GetFileNameWithoutExt" + Path.GetFileNameWithoutExtension(path));

Console.WriteLine(" GetExtension" + Path.GetExtension(path));

Console.WriteLine(" GetDirectoryName" + Path.GetDirectoryName(path));

结果如图所示:

(1)Environment类

通过 System 命名空间中的Environment 类,可以方便地获取与系统相关的信息。Environment 类的部分属性和方法如图所示。

一个例子:

//显示系统信息

Console.WriteLine(" 处理器数量: " + Environment.ProcessorCount);

Console.WriteLine(" 操作系统版本: " + Environment.OSVersion);

Console.WriteLine("公共语言运行库版本: " + Environment.Version);

Console.WriteLine(" 系统目录: " + Environment.SystemDirectory);

Console.WriteLine(" 用户域名: " + Environment.UserDomainName);

Console.WriteLine(" 用户名: " + Environment.UserName);

Console.WriteLine(" 机器名: " + Environment.MachineName);

通过 Environment 类的GetFolderPath()方法可以获取系统特殊文件夹的路径,这些特殊文件夹由SpecialFolder 枚举列出。SpecialFolder 类的部分成员如图所示。

例子:

string d = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

string p = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);

string m = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);

string f = Environment.GetFolderPath(Environment.SpecialFolder.Favorites);

//输出特殊目录路径

Console.WriteLine("我的文档:" + d);

Console.WriteLine("我的图片:" + p);

Console.WriteLine("我的音乐:" + m);

Console.WriteLine("我的收藏:" + f);

5.基于流的文件操作

数据以文件的形式存储在硬盘、光盘等存储介质上,读写数据的过程可以看作数据像水一样流入或流出存储介质,所以.NET 设计了一种叫做流(Stream)的类来读写文件。实际上一旦我们打开一个文件,它就和一个流关联起来。文件是数据源,流是传输数据的工具,通过这种专门的工具来传输数据,就可以使传输工具和数据源分离开来,从而更容易切换数据源,更容易实现不同环境下代码的重用。数据源除了文件以外,还可以是内存中数据、网络上的数据甚至是代码中的一个变量等等。与流相关的类都定义在 System.IO 命名空间中,它们大多数继承于抽象类Stream。如图展示了部分与流相关的类。

FileStream 类用于字节数据输入和输出,TextReader 和TextWriter 用于Unicode 字符的输入和输出,BinaryReader 和BinaryWriter 用于二进制数据的输入和输出。

(1)FileStream类

FileStream 是专门进行文件操作的流,使用它可对文件进行读取、写入、打开和关闭等操作,既支持同步读写操作,也支持异步(缓冲)读写操作。FileStream 类的部分属性和方法如图所示:

要使用流,必须先创建一个流的对象。请创建一个名为“StreamTest”的控制台项目,并添加如下代码。

string fileName = @"D:filestream_test.data";

//若文件不存在,则创建文件,写入数据

if(!File.Exists(fileName))

{  FileInfo myFile = new FileInfo(fileName); //创建文件

FileStream fs = myFile.OpenWrite(); //获取与文件对应的流

byte[] datas = { 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };

fs.Write(datas, 0, datas.Length); //用流写入数据

Console.WriteLine("数据已写入。");

fs.Close(); //关闭流

}

else //若文件存在,则读取数据

{   //建立与文件关联的流

FileStream fs = new FileStream(fileName, FileMode.Open, File Access.Read);

byte[] datas = new byte[fs.Length];

fs.Read(datas, 0, datas.Length); //用流读取数据

Console.WriteLine(“读取数据:”);

foreach(byte data in datas)

{  Console.Write(data);}

fs.Close();  //关闭流

}

这里我们用两种方法获取与文件对应的流。第一种办法是通过FileInfo类的OpenWrite()方法获取,OpenWrite()方法打开相应的文件,返回一个与该文件相关的FileStream对象。

当使用FileStream类的构造函数时,需要提供打开方式(FileMode)、访问模式(FileAccess)、共享模式(FileShare)等信息,它们分别用FileMode枚举、FileAccess枚举和FileShare枚举表示,这些枚举的值很容易理解,更详细的信息请参看帮助文档。与FileStream类相关的枚举如图所示:

FileStream类的Write()方法和Read()方法。Write()方法的原型如下,参数中字节数组array 是数据源,offset 和count 表示把数据源中的从位置offset 开始的count 个字节写入文件。

Read()方法的原型如下:参数中字节数组array 是目标数组,用于存储读取到的数据,存储的位置是从位置offset开始的count 个字节。

返回值为实际读取到的字节数。如果开始读取时当前位置已在流的末尾,则返回值为0。如果流中当前可用的字节数没有请求的字节数那么多,则实际读取到的字节数会小于请求的字节数。还有一个需要注意的情况是,在处理网络流(如FTP)、设备流(如串口输入)等流时,即使尚未到达流的末尾,实际读取到的字节数仍有可能少于请求的字节数。如果是从控制台读取或者从本地文件读取,很少遇到这种情况。

综上可以看出,FileStream 类主要用于向文件中写入或读取以字节为单位的数据。

2)关于流的异常

try-catch-fianlly结构,用流来操作文件也可能会出现异常,所以关于流的操作一般要放在try-catch-fianlly 结构中,以增强程序的健壮性。

string fileName = @"D:filestream_test.data";

FileStream fs = null;

try

{…}

catch(Exception e)

{ Console.WriteLine(e.Message);}

finally

{ if (fs!=null)  fs.Close(); }

注意,fs对象的声明代码要放在try语句之前,不然catch块和fianly块中无法识别。另外关闭流的操作应放在fianlly块中,以确保无论操作成功还是操作失败,流都被及时释放。

读取一个文件的内容时,真正有用的代码往往只有几行,然而为了健壮性,我们却要写上10多行的代码来处理异常和关闭文件。如何才能减少这种麻烦呢?一种办法是自己编写一个方法,把异常处理和关闭文件的代码封装进去。下面编写了一个处理文件的通用方法UniversalProcess(),只要告诉它文件路径和处理文件的具体代码,它就会自动完成异常处理和关闭文件的操作。

public delegate void MyFileProcessCode(FileStream file);

//封装文件处理

public static void UniversalProcess(string path,MyFileProcessCode doSomething)

{ FileStream fs=new FileStream(path,FileMode.Open,FileAccess.ReadWrite);

     try {  doSomething(fs);}

     catch(Exception e)  {Console.WriteLine(e.Message);}

     finally{if(fs!=null) fs.Close();}

}

可以直接通过匿名函数和一个文件地址进行访问,例如:

UniversalProcess(@"D:a.text", delegate(FileStream fs)

{

byte[] datas = { 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };

fs.Write(datas, 0, datas.Length);

});

6.using语句

为了防止出现同步问题,当一个程序读写文件时,操作系统通常都会阻止其他程序读写该文件,因此使用完毕后要及时关闭,否则就会导致其他程序不能使用该文件。除了可以在fianlly块中关闭文件(流)外,我们还可以用C#提供的using语句进行文件操作。

using(FileStream fs = File.OpenWrite(path))

 {

byte[] datas = { 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };

fs.Write(datas, 0, datas.Length);

}

在上面的代码在using后的括号里创建了一个流对象fs,然后在using后的语句块中使用该流对象。当退出using语句时,系统会自动关闭流对象fs,使用起来非常方便。using语句适用于那些需要及时释放资源的代码,其一般形式为:

using(type obj = initialization)

{ //具体处理代码}

在using后的括号中创建一个对象obj,然后在后面的大括号中使用它。当退出using语句时,对象obj会被及时销毁。using语句实际上是对try语句的封装,等价于:

{ type obj=initialization;

 try {//具体处理代码}

 finally{ if (obj!=null) { ((IDisposable)obj).Dispose();}}

}

程序先执行try块中的代码,不管是否发生异常,系统都会自动执行finally块中的代码,调用obj对象的Dispose()方法,销毁对象,释放资源。Dispose()方法和Close()方法的功能基本上是一样的,都是用来销毁对象。实际上,Close()方法就是通过调用Dispose(),方法实现的。

由此看出 using 语句不光适用于处理关于流的操作,它也适用于任何包含Dispose()方法且需要及时销毁的的对象。using 语句使用起来非常方便,如果你查看帮助文档,就会发现涉及流的例子都是用using 语句编写的。

7.用流读写文本文件

文本文件主要通过 StreamReader 和StreamWriter 来实现读写,它们分别继承于抽象类TextReader 和TextWriter。StreamReader 类的部分属性和方法,StreamWriter 类的部分属性和方法,分别所图示。

要使用 StreamReader 和StreamWriter,也必须先创建它们的对象。

StreamWriter streamWriter = null;

StreamReader streamReader = null;

try

{

//若文件不存在,则创建文件,写入数据

if(!File.Exists(@"D: ext_test.txt"))

{

FileInfo myFile = new FileInfo(@"D: ext_test.txt"); //创建文件

streamWriter = myFile.CreateText(); //获取文件对应的流

string text = @"何处望神州,满眼风光北固楼。千古兴亡多少事,悠悠。不尽长江滚滚流。年少万兜鍪,坐断东南战未休。天下英雄谁敌手?曹刘。生子当如孙仲谋。";

streamWriter.Write(text); //用流写入数据

Console.WriteLine("数据已写入.");

}

else//若文件存在,则读取数据

{  //建立与文件关联的流

streamReader = new StreamReader(@"D: ext_test.txt");

string text = streamReader.ReadToEnd(); //用流读取数据

Console.WriteLine("读取数据: " + text);

}

}

catch (Exception e)

{  //异常处理

Console.WriteLine(e.Message);

}

finally

{  //关闭流

if(streamWriter!=null)streamWriter.Close();

if(streamReader!=null)streamReader.Close();

}

这里我们用两种方法获取与文件对应的流。第一种办法是通过FileInfo 类(或File 类)的方法获取,比如FileInfo 对象myFile的CreateText()方法返回一个StreamWriter 流:StreamWriter streamWriter =myFile.CreateText(); OpenText ()方法返回StreamReader 流:StreamReader streamReader=myFile.ReadText(); 。File 类的部分与流相关的方法如图所示。

第二种办法是用 StreamReader 类或StreamWriter 类的构造函数创建流。

StreamReader streamReader = new StreamReader(fileName);

8.用流读写二进制文件

通过 BinryReader 和BinaryWriter 类实现二进制读写。BinryReader 类的部分属性和方法,BinaryWriter 类的部分属性和方法,分别如图所示:

要使用 BinryReader 和BinaryWriter 类,也必须先创建它们的对象,但它们的对象总是通过FileStream 对象来创建的。

string path = @"D:inary_test.txt";

FileStream fileStream = null;

try

{  //创建文件

fileStream = File.Create(path);

//写文件

BinaryWriter bw = new BinaryWriter(fileStream);

DateTime time = DateTime.Now;

bw.Write(time.Year);

bw.Write(time.Month);

bw.Write(time.Day);

bw.Write(time.Hour);

bw.Write(time.Minute);

bw.Write(time.Second);

//当前位置移动到开头

fileStream.Seek(0, SeekOrigin.Begin);

//读文件

BinaryReader br = new BinaryReader(fileStream);

int year = br.ReadInt32();

int month = br.ReadInt32();

int day = br.ReadInt32();

int hour = br.ReadInt32();

int minute = br.ReadInt32();

int second = br.ReadInt32();

Console.WriteLine("{0}-{1}-{2} {3}:{4}:{5}",

year, month, day, hour, minute, second);

}

catch (Exception e)

{

//异常处理

Console.WriteLine(e.Message);

}

finally

{

//释放资源

if(fileStream!=null)

fileStream.Close();

}

实际上,BinryReader 和BinaryWriter 本身并不是独立的流,它们是对其他流的包装,所以创建BinryReader 和BinaryWriter 对象时,需要传给它们一个FileStream 对象,对BinryReader 和BinaryWriter 的操作,就是对FileStream 的操作。

 

见贤思齐,见不贤而自省
原文地址:https://www.cnblogs.com/Sweepingmonk/p/10867731.html