无论要开发什么样的应用程序,我们都不可避免地要对文件进行操作,所以掌握相关知识就显得格外重要了。.Net Framework 对文件操作提供了很好的支持,它让我们的编程工作变得简单

文件操作核心类

在介绍具体的文件操作之前,你有必要先来了解文件操作的类,.Net Framework提供的文件操作类基本上都位于System.IO命名空间下,下面就详细介绍这些操作文件的核心类

File 和 FileInfo 类

这两个类用来操作硬盘上的文件,File类主要通过静态方法实现的,而FileInfo类则是通过实例方法实现的

下面该表列举了File类的核心成员

成员说明
AppendText创建一个 StreamWrite 类型,用于向文件追加文本
Create在指定路径下创建或覆盖文件
Delete删除指定文件
Exists检查指定的文件是否存在
Open指定特有的读、写访问权限打开文件
OpenRead以读取的方式打开现有文件
OpenWrite打开或创建一个现有文件,以写入文本
ReadAllText读取文件的所有行,然后关闭文件
WriteAllText向现有文件或创建的新文件中,写入指定的字符串,然后关闭文件

FileInfo类的实例成员提供了与File类差不多的功能, 如其中就包含了 AppendText、Create、OpenRead 和 OpenWrite 等方法。在大多数情况下,FileFileInfo类可以互换使用。但由于File所提哦给你的方法都是静态方法,如果只想执行一个操作,使用File方法的效率要比使用FileInfo实例方法更高。下面的代码演示了File类对文件进行操作的过程

using System;
using System.IO;
using System.Threading;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream fs = null;
            StreamWriter writer = null;
            string path = @"C://test.txt";
            if (File.Exists(path))
            {
                fs = File.Create(path);
            }
            else
            {
                fs = File.Open(path, FileMode.Open);
            }
            writer = new StreamWriter(fs);
          	writer.WriteLine("just for test");
            writer.Flush(); // 清空缓冲区,将所有的缓冲区数据写入到文件
            writer.Close(); // 关闭数据流
            fs.Close();
            Console.ReadKey();
        }
    }
}

Directory 和 DirectoryInfo 类

这两个类中都包含了一组用来创建、移动、删除和枚举所有目录或子目录的成员。下面该表类聚了Directory类所提供的一些常用成员

成员说明
CreateDirectory在指定路径创建目录和子目录
Delete删除目录
Exists检查是否存在目录
GetFiles获得目录下所有文件名称的数组
GetParent获取指定目录的父目录
GetCurrentDirectory获取应用程序的当前工作目录
Move将目录及其内容移动新位置

DirectoryInfo类所提供的成员与Directory类的相似,在大多数情况下,两个类可以呼唤使用。下面的代码示例演示了与目录相关的操作

using System;
using System.IO;
using System.Threading;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            if (!Directory.Exists("C:temp"))
            {
                Directory.CreateDirectory("C:temp");
            }
            Console.ReadKey();
        }
    }
}

以上代码首先调用了Directory.Exists静态方法检查目录是否存在,如果目录不存在就创建该目录

前面我们使用StreamWriter类来完成向文件中写入字符串的操作,文件操作离不开流的相关操作

流(Stream)可以理解为内存中的字节序列。Stream是所有流的抽象积累,每个具体的存储实体都可以通过Stream派生类来实现,如FileStream类就表示“文件”这种存储实体。同样,流也设计三个基本操作

  • 对流的读取——将流中的数据读取到具体的数据结构(如数组等)中
  • 对流进行写入——把数据结构中的数据写入到流中
  • 对流进行查找——把流内的当前位置进行查询和修改

Stream类的一些常用成员如下表所示

成员说明
CanRead检查当前流是否支持读取操作
CanSeek检查当前流是否支持查找操作
CanWrite检查当前流是否支持写入操作
Length获取用字节表示的流畅读
Position获取或设置当前流中的位置
BeginRead开始异步读操作
BeginWrite开始异步写操作
Close关闭当前流并释放与之关联的所有资源,如文件句柄资源等
EndRead等待异步读操作完成
EndWrite等待异步写操作完成
Flush清除当前流的所有缓冲区,并把缓冲区的数据写入到存储设备
Write向当前流写入字节序列,并将流的当前位置设置为写入字节数

以下 Stream 派生类是我们经常会用到的

  • NetworkStream——提供网络通信的基础数据流
  • FileStream——用于将数据以流的形式写入文件,或从文件中读取
  • MemoryStream——用于对内存中的数据进行写入或读取
  • GZipStream——提供用于压缩和解压缩流的数据

这里以FileStream为例来演示流的操作

using System;
using System.IO;
using System.Text;
using System.Threading;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:temptest.txt";
            using(FileStream fs = File.Open(path, FileMode.OpenOrCreate))
            {
                string msg = "test";
                byte[] msgArray = Encoding.Default.GetBytes(msg);
                fs.Write(msgArray, 0, msgArray.Length);
                fs.Seek(0, SeekOrigin.Begin);

                byte[] msgArray2 = new byte[msgArray.Length];
                fs.Read(msgArray2, 0, msgArray.Length);
                Console.WriteLine(Encoding.Default.GetString(msgArray2));
            }
            Console.ReadKey();
        }
    }
}

System.IO命名空间提供了不同的读写器,以对流中的数据进行操作。这些类通常是成对出现的:一个用于从流中读取数据,另一个用于向流中写入数据。不同类型的读写器分别适用于处理文本、字符串、二进制数据和流等

  • 文本读写器
    • TextReader 类
    • TextWriter 类
  • 字符串读写器
    • StringReader 类
    • StringWriter 类
  • 二进制读写器
    • BinaryReader 类
    • BinaryWriter 类
  • 流读写器
    • StreamReader 类
    • StreamWriter 类

前面,我们直接通过流对象的WriteRead方法对流数据进行了写入和读取,但更好的操作流的方式,确实通过读写器来完成这一过程。现在我们用StreamReaderStreamWriter读写器来修改代码,修改后的代码如下:

using System;
using System.IO;
using System.Text;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:temptest.txt";
            using(FileStream fs = File.Open(path, FileMode.OpenOrCreate))
            {
                string msg = "test";
                StreamWriter sw = new StreamWriter(fs);
                sw.Write(msg);

                StreamReader sr = new StreamReader(fs);
                Console.WriteLine(sr.ReadToEnd());

                sw.Close();
                sr.Close();
            }
            Console.ReadKey();
        }
    }
}

对文件进行异步操作

前面对文件进行的操作都是同步的。在同步操作中,如果向文件写入大量数据,方法将一直处于等待状态,直到写入完成。但若使用异步操作,方法就可以在写入操作的同时继续执行后面的操作。下面以 FileStream 为例,介绍对文件的异步操作方法

public FileStream (string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync);

这里,最后一个参数 useAsync 用于指定程序使用的是异步方式还是同步方式,如果设置为 true,则表示使用异步方式来操作 FileStream。对文件进行异步操作时,使用异步方式的速度可能会比同步方式要蛮。所以你需要针对应用程序的实际情况来决定是否要选择异步处理方式,下面的代码演示了对文件进行异步操作的过程

using System;
using System.IO;
using System.Text;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"C:temptest.txt";
            FileInfo file = new FileInfo(path);
            using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.None, 4096, true))
            {
                string msg = "Hello";
                byte[] buffer = Encoding.Default.GetBytes(msg);
                IAsyncResult asyncResult = fs.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(EndWriteCallback), fs);
            }
            Console.ReadKey();
        }

        static void EndWriteCallback(IAsyncResult asyncResult)
        {
            FileStream fs = asyncResult.AsyncState as FileStream;
            if (fs != null)
            {
                fs.EndWrite(asyncResult);
                fs.Close();
            }
        }
    }
}