1.7 .NET I/O操作与文件管理

1.7.1 基本的文件I/O

抽象基类Stream支持读取和写入字节。Stream集成了异步支持。其默认实现根据其相应的异步方法来定义同步读取和写入,反之亦然。

所有表示流的类都是从Stream类继承的。Stream类及其派生类提供数据源和储存库的一般视图,使程序员不必了解操作系统和基础设备的具体细节。

流涉及三个基本操作如下所示:

  • 可以从流读取。读取是从流到数据结构(如字节数组)的数据传输。
  • 可以向流写入。写入是从数据源到流的数据传输。
  • 流可以支持查找。查找是对流内的当前位置进行的查询和修改。

根据基础数据源或储存库,流可能只支持这些功能中的一部分。例如,NetworkStreams不支持查找。Stream的CanRead、CanWrite和CanSeek属性及其派生类决定不同的流所支持的操作。

1.7.2 用于文件I/O的类

Directory提供通过目录和子目录进行创建、移动和枚举的静态方法。DirectoryInfo类提供实例方法。

DirectoryInfo提供通过目录和子目录进行创建、移动和枚举的实例方法。Directory类提供静态方法。

DriveInfo提供访问有关驱动器的信息的实例方法。

File提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建FileStream。FileInfo类提供实例方法。

FileInfo提供用于创建、复制、删除、移动和打开文件的实例方法,并协助创建FileStream。File类提供静态方法。

FileStream支持通过其Seek方法随机访问文件。默认情况下,FileStream以同步方式打开文件,但它也支持异步操作。File包含静态方法,而FileInfo包含实例方法。

FileSystemInfo是FileInfo和DirectoryInfo的抽象基类。

Path提供以跨平台的方式处理目录字符串的方法和属性。

DeflateStream提供使用Deflate算法压缩和解压缩流的方法和属性。

GZipStream提供压缩和解压缩流的方法和属性。默认情况下,此类使用与DeflateStream类相同的算法,但可以扩展到使用其他压缩格式。

SerialPort提供控制串行端口文件资源的方法和属性。

File、FileInfo、DriveInfo、Path、Directory和DirectoryInfo是密封(在Microsoft Visual Basic中为NotInheritable)类。可以创建这些类的新实例,但它们不能有派生类。

1.7.3 用于从流读取和写入流的类

BinaryReader和BinaryWriter从Streams读取或向Streams写入编码的字符串和基元数据类型。

StreamReader通过使用Encoding进行字符和字节的转换,从Streams中读取字符。StreamReader具有一个构造函数,该构造函数根据是否存在专用于Encoding的preamble(例如一个字节顺序标记)来尝试确定给定Stream的正确Encoding是什么。

StreamWriter通过使用Encoding将字符转换为字节,向Streams写入字符。

StringReader从Strings中读取字符。StringReader允许用相同的API来处理Strings,因此输出可以是String或以任何编码表示的Stream。

StringWriter向Strings写入字符。StringWriter允许用相同的API来处理Strings,因此输出可以是String或以任何编码表示的Stream。

TextReader是StreamReader和StringReader的抽象基类。抽象Stream类的实现用于字节输入和输出,而TextReader的实现用于Unicode字符输出。

TextWriter是StreamWriter和StringWriter的抽象基类。抽象Stream类的实现用于字节输入和输出,而TextWriter的实现用于Unicode字符输出。

1.7.4 通用I/O流类

BufferedStream是向另一个Stream(例如NetworkStream)添加缓冲的Stream(FileStream内部已具有缓冲,MemoryStream不需要缓冲)。BufferedStream可以围绕某些类型的流来构成以提高读写性能。缓冲区是内存中的字节块,用于缓存数据,从而减少对操作系统的调用次数。

CryptoStream将数据流链接到加密转换。虽然CryptoStream是从Stream派生的,但它不属于System.IO命名空间,而是在System.Security.Cryptography命名空间中。

MemoryStream是一个非缓冲的流,可以在内存中直接访问它的封装数据。该流没有后备存储,可用做临时缓冲区。

NetworkStream表示网络连接上的Stream。虽然NetworkStream是从Stream派生的,但它不属于System.IO命名空间,而是在System.Net.Sockets命名空间中。

1.7.5 创建目录列表

下面的代码示例演示如何使用I/O类来创建目录中具有“.exe”扩展名的所有文件的列表。

using System;
using System.IO;
class DirectoryLister
{
   public static void Main(String[] args)
   {
      string path = ".";
      if (args.Length > 0)
      {
         if (File.Exists(args[0])
         {
             path = args[0];
         }
         else
         {
             Console.WriteLine("{0} not found; using current directory:",args[0]);
         }
      DirectoryInfo dir = new DirectoryInfo(path);
      foreach (FileInfo f in dir.GetFiles("*.exe"))
      {
         String name = f. Name;
         long size = f.Length;
         DateTime creationTime = f.CreationTime;
         Console.WriteLine("{0,-12:N0} {1,-20:g} {2}",size,creationTime,name);
      }
   }
}

1.7.6 对新建的数据文件进行读取和写入

BinaryWriter和BinaryReader类用于读取和写入数据,而不是字符串。下面的代码示例演示如何向新的空文件流(Test.data)写入数据及从中读取数据。在当前目录中创建了数据文件之后,也就同时创建了相关的BinaryWriter和BinaryReader,BinaryWriter用于向Test.data写入整数0到10,Test.data将文件指针置于文件尾。在将文件指针设置回初始位置后,BinaryReader读出指定的内容。

using System;
using System.IO;
class MyStream
{
   private const string FILE_NAME = "Test.data";
   public static void Main(String[] args)
   {
      // Create the new,empty data file
      if (File.Exists(FILE_NAME))
      {
         Console.WriteLine("{0} already exists!",FILE_NAME);
         return;
      }
      FileStream fs = new FileStream(FILE_NAME,FileMode.CreateNew);
      // Create the writer for data
      BinaryWriter w = new BinaryWriter(fs);
      // Write data to Test.data
      for (int i = 0; i < 11; i++)
      {
         w.Write( (int) i);
      }
      w.Close();
      fs.Close();
      // Create the reader for data
      fs = new FileStream(FILE_NAME,FileMode.Open,FileAccess.Read);
      BinaryReader r = new BinaryReader(fs);
      // Read data from Test.data.
      for (int i = 0; i < 11; i++)
      {
         Console.WriteLine(r.ReadInt32());
      }
      r.Close();
      fs.Close();
   }
}

1.7.7 打开并追加到日志文件

StreamWriter和StreamReader向流写入字符并从流读取字符。下面的代码示例打开log.txt文件(如果文件不存在则创建文件)以进行输入,并将信息附加到文件尾,然后将文件的内容写入标准输出以便显示。除此示例演示的做法外,还可以将信息存储为单个字符串或字符串数组,WriteAllText或WriteAllLines方法可以用于实现相同的功能。

using System;
using System.IO;
class DirAppend
{
   public static void Main(String[] args)
   {
      using (StreamWriter w = File.AppendText("log.txt"))
      {
         Log ("Test1",w);
         Log ("Test2",w);
         // Close the writer and underlying file
         w.Close();
      }
      // Open and read the file
      using (StreamReader r = File.OpenText("log.txt"))
      {
         DumpLog (r);
      }
   }
   public static void Log (String logMessage,TextWriter w)
   {
      w.Write("\r\nLog Entry : ");
      w.WriteLine("{0} {1}",DateTime.Now.ToLongTimeString(),DateTime.Now.ToLongDateString());
      w.WriteLine(" :");
      w.WriteLine(" :{0}",logMessage);
      w.WriteLine ("-------------------------------");
      // Update the underlying file
      w.Flush();
   }
   public static void DumpLog (StreamReader r)
   {
      // While not at the end of the file,read and write lines String line;
      while ((line=r.ReadLine())!=null)
      {
         Console.WriteLine(line);
      }
      r.Close();
   }
}

1.7.8 向文件写入文本

下面的代码示例演示如何向文本文件中写入文本。

第一个示例演示如何向现有文件中添加文本。第二个示例演示如何创建一个新文本文件并向其中写入一个字符串。WriteAllText方法可提供类似的功能。

using System;
using System.IO;
class Test
{
   public static void Main()
   {
      // Create an instance of StreamWriter to write text to a file
      // The using statement also closes the StreamWriter
      using (StreamWriter sw = new StreamWriter("TestFile.txt"))
      {
         // Add some text to the file
         sw.Write("This is the ");
         sw.WriteLine("header for the file.");
         sw.WriteLine("-------------------");
         // Arbitrary objects can also be written to the file
         sw.Write("The date is: ");
         sw.WriteLine(DateTime.Now);
      }
   }
}
using System;
using System.IO;
public class TextToFile
{
   private const string FILE_NAME = "MyFile.txt";
   public static void Main(String[] args)
   {
      if (File.Exists(FILE_NAME))
      {
         Console.WriteLine("{0} already exists.",FILE_NAME);
         return;
      }
      using (StreamWriter sw = File.CreateText(FILE_NAME))
      {
         sw.WriteLine ("This is my file.");
         sw.WriteLine ("I can write ints {0} or floats {1},and so on.",1,4.2);
         sw.Close();
      }
   }
}

1.7.9 从文件读取文本

下面的代码示例演示如何从文本文件中读取文本。

第二个示例在检测到文件结尾时向用户发出通知。通过使用ReadAllLines或ReadAllText方法也可以实现此功能。

using System;
using System.IO;
class Test
{
   public static void Main()
   {
      try
      {
         // Create an instance of StreamReader to read from a file
         // The using statement also closes the StreamReader
         using (StreamReader sr = new StreamReader("TestFile.txt"))
         {
            String line;
            // Read and display lines from the file until the end of
            // the file is reached
            while ((line = sr.ReadLine()) != null)
            {
               Console.WriteLine(line);
            }
         }
      }
      catch (Exception e)
      {
         // Let the user know what went wrong
         Console.WriteLine("The file could not be read:");
         Console.WriteLine(e.Message);
      }
   }
}
using System;
using System.IO;
public class TextFromFile
{
   private const string FILE_NAME = "MyFile.txt";
   public static void Main(String[] args)
   {
      if (!File.Exists(FILE_NAME))
      {
         Console.WriteLine("{0} does not exist.",FILE_NAME);
         return;
      }
      using (StreamReader sr = File.OpenText(FILE_NAME))
      {
         String input;
         while ((input=sr.ReadLine())!=null)
         {
            Console.WriteLine(input);
         }
         Console.WriteLine ("The end of the stream has been reached.");
         sr.Close();
      }
   }

1.7.10 从字符串中读取字符

下面的代码示例允许在现有字符串中从指定的位置开始读取一定数目的字符。使用StringReader完成此操作。

此代码定义字符串并将其转换为字符数组,然后,可以使用适当的StringReader.Read方法读取该字符数组。

本示例只从字符串中读取指定数目的字符,如下所示:

Some number o
using System;
using System.IO;
public class CharsFromStr
{
   public static void Main(String[] args)
   {
      // Create a string to read characters from
      String str = "Some number of characters";
      // Size the array to hold all the characters of the string
      // so that they are all accessible
      char[] b = new char[24];
      // Create an instance of StringReader and attach it to the string.
      StringReader sr = new StringReader(str);
      // Read 13 characters from the array that holds the string,starting
      // from the first array member
      sr.Read(b,0,13);
      // Display the output
      Console.WriteLine(b);
      // Close the StringReader
      sr.Close();
   }
}

1.7.11 向字符串写入字符

下面的代码示例把从字符数组中指定位置开始的一定数目的字符写入现有的字符串。使用StringWriter完成此操作,如下所示:

using System;
using System.IO;
using System.Text;
public class CharsToStr
{
   public static void Main(String[] args)
   {
      // Create an instance of StringBuilder that can then be modified
      StringBuilder sb = new StringBuilder("Some number of characters");
      // Define and create an instance of a character array from which
      // characters will be read into the StringBuilder
      char[] b = {' ','t','o',' ','w','r','i','t','e',' ','t','o','.'};
      // Create an instance of StringWriter
      // and attach it to the StringBuilder
      StringWriter sw = new StringWriter(sb);
      // Write three characters from the array into the StringBuilder
      sw.Write(b,0,3);
      // Display the output
      Console.WriteLine(sb);
      // Close the StringWriter
      sw.Close();
   }
}