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

1、 驱动器

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

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

 2、目录

  为了方便检索文件,须要在驱动器中先建立目录,而后把文件保存到这个目录中。在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("该目录已经存在");
}

3、 文件

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

  (一)、文件类File函数

File类的经常使用方法ui

常 用 方 法编码

介 绍

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”。

4、文件流概述

     在.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方法从文件中读取了全部的二进制字符,并将其读为整数,便于输出。

5、 综合应用

(一)、建立日志文件

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

  一、技术要点

  * 建立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();
}

  三、 源程序解读

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

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

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

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

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

一、 技术要点

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

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

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

二、 实现代码

//表示日志文件路径及文件名称的字符串
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参数指定是否覆盖文件。

一、 技术要点

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

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

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

二、 实现代码

//源文件路径及文件名
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”文件一致。

相关文章
相关标签/搜索