进阶系列(4)—— C#文件与流

一、 驱动器

     在Windows操作系统中,存储介质统称为驱动器,硬盘由于可以划分为多个区域,每一个区域称为一个驱动器。.NET Framew   ork提供DriveInfo类和 DriveType枚举型,以方便在程序中直接使用驱动器。DriveInfo类的常用字段成员有DriveFormat(文件系统格式,如NTFS或FAT32)、DriveType(驱动器类型)、Name(驱动器名)、TotalSize(总空间)、TotalFreeSpace(获得驱动器可用空间)。常用的方法成员有GetDrives(获得可用驱动器列表)。

    DriveType枚举型的枚举值有CDRom(光驱)、Fixed(硬盘)、Network(网络驱动器)和Removeable(软盘或U盘)等。例如,以下代码可以输出每一个硬盘驱动器的剩余空间信息。

DriveInfo[] drivers = DriveInfo.GetDrives();
foreach(DriveInfo driver in drivers)
{
    if(driver.DriveType == DriveType.Fixed && driver.DriveFormat == "NTFS")
    {
        Console.WriteLine("在{0}驱动器上还有{1}字节的剩余空间。", driver.Name, driver.AvailableFreeSpace);
    }
}
Console.ReadLine();

 二、目录

  为了方便检索文件,需要在驱动器中先创建目录,然后把文件保存到这个目录中。在Windows操作系统中,目录又称文件夹。每个驱动器都有一个根目录,使用”\”表示,如”C:\”表示C驱动器的根目录。创建在根目录中的目录称为一级子目录。在一级子目录中创建的目录称为二级子目录,依此类推。文件系统的目录结构是一种树形结构。

  .NET Framework提供了Directory类和DirectoryInfo类,以方便在程序中直接操作目录。

  Directory类的常用方法成员有CreateDirectory(创建新目录)、Delete(删除目录)、Exists(判断目录是否存在)、Move(移动目录)、GetFiles(获得目录的文件列表)、GetDirectories(获得子目录列表)等。

  DirectoryInfo类的常用字段成员有Name(提取目录名)、Exists(判断目录是否存在)、Parent(父目录)、Root(根目录)、MoveTo(移动目录)、GetFiles(获得目录的文件列表)、GetDirectories(获得子目录列表)等。例如,以下代码分别展现了Directory类和DirectoryInfo类的基本方法。

Directory.CreateDirectory(@"d:\C#程序设计");
if(Directory.Exists(@"d:\C#程序设计"))
{
    Console.WriteLine("创建成功");
}
Directory.Delete(@"d:\C#程序设计");
if (!Directory.Exists(@"d:\C#程序设计"))
{
    Console.WriteLine("删除成功");
}

DirectoryInfo dir = new DirectoryInfo(@"d:\C#程序设计");
if (!dir.Exists)
{
    dir.Create();
}
else
{
    Console.WriteLine("该目录已经存在");
}

三、 文件

    .NET Framework提供了File类和FileInfo类,以方便在程序中直接操作文件。File和FileInfo类位于System.IO命名空间,都可以用来实现创建、复制、移动、打开文件等操作。File类和FileInfo类与Directory类和DirectoryInfo类的工作方式相似。File类是一个静态类,可直接调用其方法成员。FileInfo类不是静态类,需要先创建实例

  (一)、文件类File

File类的常用方法

常 用 方 法

介 绍

Open()

打开文件

Create()

创建文件

Copy()

复制文件

Delete()

删除文件

Exists()

判断文件是否存在

Move()

移动文件

Replace()

替换文件

AppendAllText()

新建文件并添加文本

ReadAllText()

打开并读取文本内容

  下面通过一个示例演示File类的用法。

(1)  创建一个名为FileCreate的控制台应用程序项目

(2)  修改Program.cs文件中的Main方法的内容如下:       


 //设置所要创建文件的绝对路径
string path = @"d:\test.txt";
//以路径为参数创建文件
File.Create(path);
代码中变量path给出类文件的路径,利用File类的Create方法创建类该文件。查看D盘根目录,会有一个新的test.txt的文档出现。

  (二)、 文件信息类 FileInfo

     文件信息类FileInfo与File类不同,它虽然也提供类创建、复制、删除、移动和打开文件的方法,并且帮助创建FileStream对象,但是它提供的仅仅是实例方法。

FileInfo类常用字段

常用字段

介绍

Name

提取文件名

Directory

所属目录

Exists

是否存在(继承自父类FileSystemInfo)

Extension

文件扩展名

Length

文件长度

IsReadOnly

是否为只读

FileInfo类常用方法

常用方法

介绍

Open()

打开文件

Create()

创建文件

CopyTo()

复制到新文件

Delete()

删除文件

MoveTo()

移动文件

Replace()

替换文件

EnCrypt()

加密文件

Decrypt()

解密文件

  因此要使用FileInfo类,必须先实例化一个FileInfo对象。FileInfo类的常用方法与File类基本相同。

  与文件类File和文件夹类Directory相比,文件信息类FileInfo和文件夹信息类DirectoryInfo具有其大部分功能。

  * File类和Directory类适合对不同的对象进行单一的处理。此种特殊情况下,静态方法的调用速度比较快,不用进行实例化

  * FileInfo类和DirectoryInfo类适合用于对同一文件或文件夹进行多种操作的情况。此种情况下,实例化后的对象不需要每次都寻找文件,可以直接对该文件进行操作。

(三)、 路径

       每个驱动器包含一个或多个目录,而每个目录又可以包含一个或多个子目录,目录的结构为树形结构。一个文件只能保存在树形结构的某个特定的目录中,文件所在位置为路径。要检索文件时,必须首先确定文件的路径。路径由驱动器盘符、目录名、文件名、文件扩展名和分隔符组成,有两种种表示方法:一种是从驱动器的根目录开始书写,如C:\Windows\System32\notepad.exe,这种路径称为绝对路径;另一种是从当前目录位置开始书写,如System32\notepad.exe(假设当前目录为C:\Windows),这种路径称为相对路径。

      在C#中,使用文件和目录路径时要十分谨慎。C#将反斜杠”\”字符视作转义符,因此当路径表示为字符串时,要使用两个反斜杠表示,例如:

   “C:\\Windows\\System32\\notepad.exe”

   另外,C#允许在字符串前添加”@”标志,以提示编译器不要把”\”字符视作转义符,而视作普通字符,例如:

     @”C:\Windows\System32\notepad.exe”

      .NET Framework提供了Path类,以帮助在程序中管理文件和目录路径,Path类位于System.IO命名空间,是一个静态类,可以用来操作路径的每一个字段,如驱动器盘符、目录名、文件名、文件扩展名和分隔符等。Path类的常用字段成员有PathSeperator(路径分隔符,如”;”)、DirectorySeparatorChar(目录分隔符,如”\”)、VolumeSeparator(卷分隔符,如”:”)、AltDirectorySeparator(替换目录分隔符,如”/”),常用的方法成员有GetDirectoryName(取目录名)、GetFileName(取文件名)、GetExtension(取文件扩展名)、GetFullPath(取完整路径)、GetTempPath(取操作系统的临时文件路径)等,例如,以下代码表示提取并显示路径中的目录名和文件名。

string path = @"c:\windows\System32\notepad.exe";
Console.WriteLine(Path.GetDirectoryName(path));
Console.WriteLine(Path.GetFileName(path));

       其中,目录名为”C:\Windows\System32”,文件名为”notepad.exe”。

四、文件流概述

     在.NET Framework中,文件和流是有区别的。文件是存储在磁盘上的数据集,它具有名称和相应的路径。当打开一个文件并对其进行读/写时,该文件就称为流(stream)。但是,流不仅仅是指打开的磁盘文件,还可以是网络数据。.Net Framework允许在内存中创建流。此外,在控制台应用程序中,键盘输入和文本显示都是流。流包括以下基本操作:

  * 读取(read):把数据从流传输到某种数据结构中,如输出到字符数组中。

  * 写入(write):把数据从某种数据结构传输到流中,如把字节数组中的数据传输到流中。

  * 定位(seek):在流中查找或重新定位当前位置。

(一)、操作流的类

  1. Stream类

     Stream类是所有流的抽象基类。Stream类的主要属性有CanRead(是否支持读取)、CanSeek(是否支持查找)、CanTimeout(是否可以超时)、CanWrite(是否支持写入)、Length(流的长度)、Position(获取或设置当前流中的位置)、ReadTimeout(获取或设置读取操作的超时时间)、WriteTimeout(获取或设置写操作的超时时间),主要方法有BeginRead(开始异步读操作),BeginWrite(开始异步写操作)、Close(关闭当前流)、EndRead(结束异步读操作)、EndWrite(结束异步写操作)、Flush(清除流的所有缓冲区并把缓冲数据写入基础设备)、Read(读取字节序列)、ReadByte(读取一个字节)、Seek(设置查找位置)、Write(写入字节序列)、WriteByte(写入一个字节)。 

  2. FileStream、MemoryStream和BufferedStream类

      文件流类FileStream以流的形式读、写、打开、关闭文件。另外,它还可以用来操作诸如:管道、标准输入/输出等其他与文件相关的操作系统句柄。

      内存流MemoryStream类用来在内存中创建流,以暂时保持数据,因此有了它就无须在硬盘上创建临时文件。它将数据封装为无符号的字节序列,可以直接进行读、写、查找操作。

      缓冲流BufferedStream类表示把流先添加到缓冲区,再进行数据的读/写操作。缓冲区是存储区中用来缓存数据的字节块。使用缓冲区可以减少访问数据时对操作系统的调用次数,增强系统的读/写功能。

     注意,FileStream类也有缓冲功能,在创建FileStream类的实例时,只需要指定缓冲区的大小即可。

  3. StreamReader和StreamWriter类

  流读取器StreamReader类用来以一种特定的编码(如:UTF-8)从字节流中读取字符,流写入器StreamWriter类用来以一种特定的编码(如:UTF-8)向流中写入字符。StreamReader和StreamWriter类一般用来操作文本文件。

  4. BinaryReader和BinaryWriter类

  BinaryReader类用特定的编码将基元数据类型读作二进制。BinaryWriter类以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。

  5. 文件流类 FileStream

  文件流类FileStream公开了以文件为主的Stream,既支持同步读/写操作,也支持异步读/写操作,FileStream类的特点是操作字节和字节数组。这种方式不适合操作用字符数据构成的文本文件,适合处理非文本文件。FileStream类提供了对文件的低级而复杂的操作,因此能够实现更多高级的功能。

  下面演示FileStreamWriter类的基本用法:

//要写入文件的字符数组
char[] m_cDataWrite = new char[100];
//包含要写入该流的数据的缓冲区
byte[] m_bDataWrite = new byte[100];           
           
try
{
    //创建d:\file.txt的FileStream对象
    FileStream m_FileStream = new FileStream(@"d:\file.txt", FileMode.OpenOrCreate);
    //将要写入的字符串转换成字符数组
    m_cDataWrite = "test filestream".ToCharArray();
 
    //通过UTF-8编码方法将字符数组转成字节数组
    Encoder m_Enc = Encoding.UTF8.GetEncoder();
    m_Enc.GetBytes(m_cDataWrite, 0, m_cDataWrite.Length, m_bDataWrite, 0, true);
 
    //设置流当前位置为文件开始位置
    m_FileStream.Seek(0, SeekOrigin.Begin);
    //将字节数组中的内容写入文件
    m_FileStream.Write(m_bDataWrite, 0, m_bDataWrite.Length);
    if (m_FileStream != null)
    {
        //清除此流的缓冲区,使得所有缓冲的数据都写入到文件中
        m_FileStream.Flush();
        m_FileStream.Close();
    }
}
catch (Exception ex)
{
    Console.WriteLine("There is an IOException");
    Console.WriteLine(ex.Message);
}
Console.WriteLine("Write to File Succeed!");

   代码中首先给出了文件夹的路径,利用Write方法向文件中写入部分字符串。

  下面演示FileStreamReader类的基本用法:

//要写入文件的字符数组
char[] m_cDataWrite = new char[100];
//包含要写入该流的数据的缓冲区
byte[] m_bDataWrite = new byte[100];
 
try
{
    //创建d:\file.txt的FileStream对象
    FileStream m_FileStream = new FileStream(@"d:\file.txt", FileMode.Open);
 
    //设置流当前位置为文件开始位置
    m_FileStream.Seek(0, SeekOrigin.Begin);
    //将文件的内容存到字节数组中(缓存)
    m_FileStream.Read(m_bDataWrite, 0, 100);               
}
catch (Exception ex)
{
    Console.WriteLine("There is an IOException");
    Console.WriteLine(ex.Message);
}
 
//通过UTF-8编码方法将字符数组转换成字符数组
Decoder m_Dec = Encoding.UTF8.GetDecoder();
m_Dec.GetChars(m_bDataWrite, 0, m_bDataWrite.Length, m_cDataWrite, 0);
Console.WriteLine("Read from file Succeed!");
Console.WriteLine(m_cDataWrite);

   代码中首先给出了文件夹的路径,利用Read方法从文件中读取了部分字符串。

  6. StreamWriter和StreamReader类

   应用FileStream类需要许多额外的数据类型转换操作,十分影响效率。StreamWriter类允许直接将字符和字符串写入文件。下面演示其用法:

try
{
    //保留文件现有数据,以追加写入的方式打开d:\file.txt文件
    StreamWriter m_SW = new StreamWriter(@"d:\file.txt", true);
    //向文件写入新字符串,并关闭StreamWriter
    m_SW.WriteLine("Another File Operation Method");
    m_SW.Close();               
}
catch (Exception ex)
{
    Console.WriteLine("There is an IOException");
    Console.WriteLine(ex.Message);
         }
  StreamWriter类提供了另一种从文件中读取数据的方法,下面演示其用法:
try
{
    //以绝对路径方式构造新的StreamReader对象
    StreamReader m_SR = new StreamReader(@"d:\file.txt");
 
    //用ReadToEnd方法将d:\file.txt中的数据全部读入到字符串m_Data中,并关闭StreamReader
    string m_Data = m_SR.ReadToEnd();
    m_SR.Close();
    Console.WriteLine(m_Data);
}
catch (Exception ex)
{
    Console.WriteLine("There is an IOException");
    Console.WriteLine(ex.Message);
} 

  7. BinaryReader和BinaryWriter类

  BinaryWriter类是除了FileStream和StreamWriter类之外另一种向文件写入数据的方式,与之前两种方式不同,BinaryWriter类将基础数据(如:字符串)以二进制形式写入文件流中,并支持用特定的编码写入。下面演示其用法:

FileStream m_FS = new FileStream(@"d:\data.dat", FileMode.Create);
//通过文件流创建相应的BinaryWriter
BinaryWriter m_BW = new BinaryWriter(m_FS);
for(int i = 0; i < 11; i++)
{
    //向d:\data.dat中写入数据
    m_BW.Write((int)i);
}
 
m_BW.Close();
m_FS.Close();

  代码中首先给出了文件夹的路径,利用BinaryWriter类的Write方法向文件中写入部分二进制字符。该文件是以二进制存储的,因此用记事本打开时,将无法观察到正确的字符,必须使用支持二进制的文本阅读器。

  BinaryReader类是和BinaryWriter类相对应的二进制数据读取类。它用特定的编码将基元数据类型(如:字符串类型)读作二进制值。

FileStream m_FS = new FileStream(@"d:\data.dat", FileMode.Open, FileAccess.Read);
//通过文件流创建相应的BinaryReader
BinaryReader m_BR = new BinaryReader(m_FS);
//从d:\data.dat中读取数据
for(int i = 0; i < 11; i++)
{
    Console.WriteLine(m_BR.ReadInt32());
}
 
m_BR.Close();
m_FS.Close();
 
Console.ReadLine();

   代码中首先给出了文件夹的路径。利用BinaryReader类的ReadInt32方法从文件中读取了所有的二进制字符,并将其读为整数,便于输出。

五、 综合应用

(一)、创建日志文件

  日志文件的作用是记录程序运行事件。通常使用文本文件保存数据。日志文件需要程序自动创建,并在指定的事件发生时,使用特定的格式把事件的相关数据记录到日志文件中。

  1、技术要点

  * 创建FileStream类实例时,能够通过该类构造函数的参数,指定打开文件的方式和读/写访问的方式。通过指定打开方式,实现日志文件的自动创建。

  * 使用StreamWriter类实例写入文件时,因为部分数据可能由于系统缓慢而未能及时写入,所以在所有的写入操作完成后,需要调用Flush方法将缓冲区的文件内容更新到日志文件中。

  * 使用StreamWriter类实例写入文件时,写入的方式与Console类似,可以使用WriteLine向文件中写入一行文本数据。

  2 、实现代码

const string _FILENAME = @"..\..\logfile.txt";
static void Main()
{
    //从指定的目录以打开或者创建的形式读取日志文件
    using (FileStream fs = new FileStream(_FILENAME, FileMode.OpenOrCreate, FileAccess.Write))
    {
        //创建日志文件的写入流
        StreamWriter sw = new StreamWriter(fs);
        //向日志文件写入日志信息
        Log("日志文件创建成功", sw);
        //关闭日志文件写入流
        sw.Close();
        Console.WriteLine("日志文件已创建");
    }
 
    //读取并显示日志文件
    using (StreamReader sr = new StreamReader(_FILENAME, Encoding.UTF8))
    {
        string strContent = sr.ReadToEnd();
        sr.Close();
        Console.WriteLine(strContent);
    }
 
    Console.ReadLine();
}
 
static void Log(String message, TextWriter tw)
{
    tw.Write("Log Entry:");
    tw.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
    tw.WriteLine(" :");
    tw.WriteLine(" :{0}", message);
            tw.WriteLine("----------------------------------");
            //将缓冲区中的内容更新到日志文件中
   tw.Flush();
}

  3、 源程序解读

  (1)程序引用了System.IO命名空间,在程序头部应添加对该命名空间的引用。

  (2)程序中定义了表示文件路径的常量_FILENAME

  (3)在创建FileStream类实例时,使用FileMode.OpenOrCreate模式,即文件不存在时就创建,存在时就打开已存在的文件。

(二)、对日志文件的读/写操作

  日志文件的读/写和文本文件的读/写方法基本相同,日志文件除了使用StreamReader类和StreamWriter类的实例进行读/写外,还有一些记录事件的要求。例如,在写入数据时使用追加的方式、控制日志文件的大小等。

1、 技术要点

  * 使用FileInfo类实例获取日志文件的大小,实现当日志文件的大小超出指定范围时清空日志数据的功能。并使用该类实例的OpenWrite方法,创建FileStream类实例进行写入文件的操作,实现日志文件的自动创建功能。

  * 使用StreamWriter类中定义的Seek方法,将写入位置移动到文件末尾,实现将数据以追加方式写入日志文件的功能。

  * 使用StreamReader类中定义的Peek方法,判断读取器是否已经读到日志文件的末尾。

2、 实现代码

//表示日志文件路径及文件名称的字符串
const string FILENAME = @"..\..\logfile.txt";
static void Main(string[] args)
{
    //写入日志信息
    WriteLogFile(FILENAME, "日志信息一");
    //读取日志文件
    Console.WriteLine(ReadLogFile(FILENAME));
    Console.ReadLine();
}
 
static string ReadLogFile(string FileNameWithPath)
{
    //从指定的目录以打开或创建的形式读取日志文件
    FileStream fs = new FileStream(FileNameWithPath, FileMode.OpenOrCreate, FileAccess.Read);
    //定义输出字符串
    StringBuilder output = new StringBuilder();
    //初始化该字符串的长度为0
    output.Length = 0;
    //为上面创建的文件流创建读取数据流
    StreamReader read = new StreamReader(fs);
    //设置当前流的起始位置为文件流的起始点
    read.BaseStream.Seek(0, SeekOrigin.Begin);
    //读取文件
    while(read.Peek() > -1)
    {
        //取文件的一行内容并换行
        output.Append(read.ReadLine() + "\n");
    }
    //关闭释放读数据流
    read.Close();
    //返回读到的日志文件内容
    return output.ToString();
}
 
static void WriteLogFile(string FileNameWithPath, string Message)
{
    //定义文件信息对象
    FileInfo finfo = new FileInfo(FileNameWithPath);
    //判断文件是否存在以及是否大于2K
    if(finfo.Exists && finfo.Length > 2048)
    {
        //删除该文件
        finfo.Delete();
    }
    //创建只写文件流
    using(FileStream fs = finfo.OpenWrite())
    {
        //根据上面创建的文件流创建写数据流
        StreamWriter w = new StreamWriter(fs);
        //设置写数据流的起始位置为文件流的末尾
        w.BaseStream.Seek(0, SeekOrigin.End);
        //写入"Log Entry:"
        w.Write("Log Entry:");
        //写入系统的当前时间并换行
        w.Write("{0} {1} \r\n", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
        //写入日志内容并换行
        w.Write(Message + "\r\n");
        //写入-------------------------并换行
        w.Write("----------------------\r\n");
        //清空缓冲区内容,并把缓冲区内容写入基础流
        w.Flush();
        w.Close();
    }

 运行结果如下:

Log Entry:14:26:09 2017年5月1日

日志信息一

3 、源程序解读

  (1)本示例程序使用写日志文件的WriteLogFile方法向文件中写入一条信息数据,再通过读取日志文件的ReadLogFile方法将日志文件的数据显示出来。本示例程序的流程图如下所示:                                                                                       图1 对日志文件读/写操作示例程序流程图

  (2)在写入日志文件的WriteLogFile方法中,首先打开并判断日志文件的大小是否超出了指定的尺寸。如果超出了指定的尺寸,就先将日志文件删除。然后通过FileInfo类实例的OpenWrite方法创建只写文件流,向该流中写入日志数据。

  (3)在读取日志文件的ReadLogFile方法中,首先创建一个StringBuilder类的实例,用来获取日志文件中的文本数据。接着使用StreamReader类定义的BaseStream属性中的Seek方法,将读取器位置定位在流的开始位置,然后循环读取日志文件中的文本数据,并追加到StringBuilder类实例中,读取过程中,通过StreamReader类中定义的Peek方法判断是否读到文件末尾。

(三)、复制文件

  静态File类中提供了许多操作文件的方法,使用Copy方法复制文件是比较常见的一种操作,调用Copy方法时,可以使用overwrite参数指定是否覆盖文件。

1、 技术要点

  * 使用静态类File的Exists方法判断文件是否存在。

  * 使用静态类File的Copy方法实现复制文件的功能,当文件存在时,通过指定override参数覆盖原有文件。

  * 复制文件是系统操作,为了保证程序的稳定性,在复制文件的过程中需要捕获并处理异常。

2、 实现代码

//源文件路径及文件名
const string SOURCEFILENAME = @"..\..\myfile.txt";
//目标文件路径及文件名
const string DESTINATIONFILENAME = @"..\..\result.txt";
static void Main(string[] args)
{
    try
    {
        //判断源文件是否存在
        if(!File.Exists(SOURCEFILENAME))
        {
            Console.WriteLine("找不到源文件");
        }
        else if (File.Exists(DESTINATIONFILENAME))
        {
            Console.Write("目标文件已经存在,是否覆盖?(Y/N)");
            if(Console.ReadKey(false).Key == ConsoleKey.Y)
            {
                //覆盖文件
                File.Copy(SOURCEFILENAME, DESTINATIONFILENAME, true);
                Console.WriteLine("复制文件完成");
            }
            else
            {
                Console.WriteLine("取消复制文件");
            }
        }
        else
        {
            //直接复制
            File.Copy(SOURCEFILENAME, DESTINATIONFILENAME);
            Console.WriteLine("复制文件完成");
        }
    }
    catch (Exception)
    {
        Console.WriteLine("复制文件失败");
    }
    Console.ReadLine();
}

 3 、源程序解读

  (1)本示例使用File静态类的方法实现文件的复制操作。首先判断源文件是否存在,如果源文件不存在,不作任何处理就返回。接着判断目标文件是否存在,如果目标文件不存在,就直接复制文件,否则就询问是否覆盖现有的目标文件,当用户选择覆盖时,使用源文件覆盖目标文件。

  (2)在复制文件的方法调用时,将复制文件的代码放在一个try...catch结构中,以便捕获并处理复制文件时出现的异常。

  (3)程序执行后,将程序文件所在目录下生成一个名为”result.txt”的文本文件。内容与”myfile.txt”文件一致。

posted @ 2018-09-02 19:46  StrugglingDave  阅读(1285)  评论(0编辑  收藏  举报