# CSharp文件

文件是存储数据的一种便利方式,读写文件是操作文件的基本方式,文件类包含在System.IO名称空间中,下面是用于访问文件系统的类:

说明
File 静态工具类,用于移动,复制和删除文件
Directory 静态工具类,用于移动,复制和删除目录
Path 用于处理路径名称
FileInfo 存储磁盘上的物理文件相关信息
DirectoryInfo 存储磁盘上的物理目录相关信息
FileSystemInfo FileInfo和DirectoryInfo的基类,可同时处理文件和目录
FileSystemWatcher 用于监控文件和目录,提供文件或目录发生变化时可用的事件

另外System.IO.Compression名称空间能读写压缩文件,主要类:

说明
DeflateStream 写入时自动压缩数据或读取时自动解压缩的流,使用Deflate算法实现压缩
GZipStream 写入时自动压缩数据或在读取时自动解压缩的流,使用GZIP算法实现压缩

# File类

下面一个实例列举了File类的常用方法:

static void Main(string[] args)
{
    string sourceFileName = "readme.txt";
    //File.Open("readme.txt",FileMode.Open);
    //创建一个文件来写入UTF-8字符串,如果存在就重写这个文件。
    using (StreamWriter fop1 = File.CreateText(sourceFileName)) {
        for(int i = 0; i < 10; i++)
        {
            fop1.WriteLine("hello number:"+i);
        }
    }
    //使用using不用再使用下面的Close(),简化了操作。
    //fop1.Close();
    using (StreamReader fop2 =  File.OpenText(sourceFileName))
    {
        string s;
        while ((s = fop2.ReadLine()) != null)
        {
            Console.WriteLine("Line Content:"+s);
        }
    }

    string moveTargetFileName="MyTest.txt";
    string copyTargetFileName="Readme2.txt";
    FileInfo fInfo = new FileInfo(copyTargetFileName);
    if (fInfo.Exists)
    {
        fInfo.Delete();
        File.Copy(sourceFileName, copyTargetFileName);
    }
    if (File.Exists(moveTargetFileName))
    {
        File.Delete(moveTargetFileName);
        File.Move(sourceFileName, moveTargetFileName);
    }

    FileInfo fInfo2 = new FileInfo(moveTargetFileName);
    Console.WriteLine("获取文件创建的非UTC版时间:");
    Console.WriteLine(fInfo2.CreationTime);

    Console.WriteLine("获取文件创建的UTC版时间:");
    Console.WriteLine(fInfo2.CreationTimeUtc);

    Console.WriteLine("获取文件的扩展名:");
    Console.WriteLine(fInfo2.Extension);

    Console.WriteLine("获取文件的名字:");
    Console.WriteLine(fInfo2.Name);

    Console.WriteLine("获取文件的完整路径:");
    Console.WriteLine(fInfo2.FullName);

    Console.WriteLine("获取上次访问文件的时间:");
    Console.WriteLine(fInfo2.LastAccessTime);

    Console.WriteLine("获取上次写文件的时间:");
    Console.WriteLine(fInfo2.LastWriteTime);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

上面查看一个文件是否存在时使用了两种方法,一种是使用FileInfo,查看实例的Exists属性。另一种是直接使用File.Exists()。这两种有不一样的应用场景:

  1. 如果仅进行单一方法调用,可使用静态File类上的方法,这样会快些,因为 .Net Framework不必实例化新对象,再调用方法。
  2. 如果应用程序在文件上执行几种操作,则实例化FileInfo对象则会快些,因为使用对象可以不用像静态类那样每次都寻找文件。

# Directory类

static void GetCurrentWorkFolder()
{
   Console.WriteLine("获取当前工作目录:");
   Console.WriteLine(Directory.GetCurrentDirectory());
}

static void PrintContent(string[] files)
{
   foreach(var one in files)
   {
       Console.WriteLine(one);
   }
}
static void Main(string[] args)
{
   string targetWorkFolder = @"D:\fileLearn1\MyLesson1\CSharp\";

   GetCurrentWorkFolder();

   Console.WriteLine("按目标路径创建目标目录:");
   Directory.CreateDirectory(targetWorkFolder);

   Console.WriteLine("设置目标目录为当前工作目录:");
   Directory.SetCurrentDirectory(targetWorkFolder);

   GetCurrentWorkFolder();



   DirectoryInfo dInfo = new DirectoryInfo(targetWorkFolder);
   Console.WriteLine("目标文件夹创建时间:");
   Console.WriteLine(dInfo.CreationTime);
   Console.WriteLine("目标文件夹上次访问时间:");
   Console.WriteLine(dInfo.LastAccessTime);

   (File.Create("file1")).Close();
   (File.Create("file2")).Close();

   Directory.CreateDirectory("folder1");
   Directory.CreateDirectory("folder2");
   Directory.CreateDirectory("folder3");

   Console.WriteLine("获取该目录下的所有文件:");
   string[] files = Directory.GetFiles(targetWorkFolder);
   PrintContent(files);

   Console.WriteLine("获取该目录下的所有目录:");
   string[] folders = Directory.GetDirectories(targetWorkFolder);
   PrintContent(folders);

   Console.WriteLine("获取该目录下的所有文件:");
   string[] BothFileAndFolder = Directory.GetFileSystemEntries(targetWorkFolder);
   PrintContent(BothFileAndFolder);


   FileInfo fInfo = new FileInfo("file1");
   Console.WriteLine("包含file1的目录:");
   Console.WriteLine(fInfo.Directory);
   Console.WriteLine("包含file1的目录:");
   Console.WriteLine(fInfo.DirectoryName);
   Console.WriteLine("file1的大小:");
   Console.WriteLine(fInfo.Length);
   Console.WriteLine("file1是否只读:");
   Console.WriteLine(fInfo.IsReadOnly);

   Console.WriteLine("目标目录的父级目录:");
   Console.WriteLine(dInfo.Parent);
   Console.WriteLine("目标目录的根目录:");
   Console.WriteLine(dInfo.Root);

   Console.WriteLine("删除目标目录!");
   Directory.SetCurrentDirectory(@"D:\");
   //Directory.Delete(targetWorkFolder);
   dInfo.Delete(true);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

为了避免把上面的""解释成为转义字符,需要在字符串前加@前缀,否则需要用"\"替代"",以避免转义。

# 文件操作实例

这个实例更改目标目录及子目录中文件的名字,将所有以ini为前缀的文件改成File前缀。

class Program
{
  static void Main(string[] args)
  {
      Console.WriteLine("Input Work Path:");
      string filePath = Console.ReadLine();
      Console.WriteLine("current work path:" + filePath);
      GetFile(filePath);
      Console.ReadKey();

  }
  public static void GetFile(string path)
  {
      DirectoryInfo dir = new DirectoryInfo(path);
      FileInfo[] filesInfo = dir.GetFiles();
      DirectoryInfo[] dirsInfo= dir.GetDirectories();
      foreach (FileInfo f in filesInfo)
      {
          string currentFileName = f.Name;
          if (currentFileName.StartsWith("ini"))
          {
              string changedFileName = "File"+currentFileName.Substring(3);
              File.Move(path + "/" + currentFileName, path + "/" + changedFileName);
              Console.WriteLine($"{currentFileName} changed to {changedFileName}");
          }
      }
      //获取子文件夹内的文件列表,递归遍历  
      foreach (DirectoryInfo d in dirsInfo)
      {
          Console.WriteLine("traverse directory:" + d.FullName);
          GetFile(d.FullName);
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

#

.Net 中进行的所有输入和输出工作都要用到流(stream)。流是序列化设备(serial device)设备的抽象表示,序列化设备可以线性的方式存储数据,并可按同样的方式访问,一次访问一个字节。这样的设备可以是磁盘文件,网络通道,内存位置或键盘等等。在生活中可以把流想象成是管道,数据就是管道中的水。下面是常见类:

类名 说明
FileStream 可同步或异步地读写文件
StreamReader 从流中读取字符数据,可使用FileStream作为基类创建
StreamWriter 向流中写入字符数据,可使用FileStream作为基类创建

# FileStream

FileStream指向磁盘或网络路径上的文件的流,这个类提供了在文件中读写字节的方法,但经常使用StreamReader或StreamWriter执行这些功能,因为FileStream操作的对象类别是字节,而Stream类操作对象的类别是字符,操作字节可以实现随机访问,但是它转换成字符才能被人理解,所以没有Stream类方便。FileStream类只能处理原始字节(raw byte),这样它可用于处理任何数据,比如图像和声音的文件而不仅是文本文件,但是它不能直接读入字符串,需要将字节数组先转换成字符数组才能继续。

FileAccess枚举控制着读写权限:

成员 说明
Read 只读
Write 只写
ReadWrite 可读写

如果省略指定该字段,它默认是ReadWrite。

FileMode枚举控制着读写模式:

成员 文件存在 文件不存在
Append 打开文件,流指向文件的末尾处,只能与FileAccess.Write结合使用 创建一个新文件,只能与FileAccess.Write结合使用
Create 删除文件,然后创建新文件 创建新文件
CreateNew 抛出异常 创建新文件
Open 打开文件,流指向开头 抛出异常
OpenOrCreate 打开文件,流指向开头 创建新文件
Truncate 打开文件,清除其内容,流指向文件开头处,保留文件的初始创建日期 抛出异常

下面来看下FileStream读操作的实例:

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

namespace Lesson1
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] byteData = new byte[100];
            char[] charData = new char[100];
            try
            {
                FileStream ReadFile = new FileStream(@"..\..\..\Program.cs", FileMode.Open);
                ReadFile.Seek(100, SeekOrigin.Begin);
                ReadFile.Read(byteData, 0, 100);
            }catch(IOException e)
            {
                Console.WriteLine("An IO Exception :");
                Console.WriteLine(e.ToString());
                Console.ReadKey();
                return;
            }

            Decoder DE = Encoding.UTF8.GetDecoder();
            DE.GetChars(byteData, 0, byteData.Length, charData, 0);
            Console.WriteLine(charData);
            Console.ReadKey();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

下面是FileStream写操作的实例:

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

namespace Lesson1
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] byteData;
            char[] charData;
            try
            {
                FileStream WriteFile = new FileStream(@"Temp.txt", FileMode.Create);
                WriteFile.Seek(100, SeekOrigin.Begin);
                charData = "hello world,you are unique!".ToCharArray();
                byteData = new byte[charData.Length];
                Encoder EN = Encoding.UTF8.GetEncoder();
                EN.GetBytes(charData, 0, charData.Length, byteData, 0,true);
                WriteFile.Write(byteData, 0, byteData.Length);
                WriteFile.Close();
            }catch(IOException e)
            {
                Console.WriteLine("An IO Exception :");
                Console.WriteLine(e.ToString());
                Console.ReadKey();
                return;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# StreamWriter对象

StreamWriter不像FileStream有FileMode参数,它有一个布尔值来控制是否追加文件,如果是false则创建一个新文件,如果文件已存在则清空并打开,如果为true则打开文件,保留原来的数据,并追加数据。来看实例:

static void Main(string[] args)
{
   try
   {
       //第一种方法从FileStream中创建
       //FileStream FStream = new FileStream("Temp.txt", FileMode.OpenOrCreate);
       //StreamWriter sw = new StreamWriter(FStream);

       //第二种方法直接从StreamWriter创建
       StreamWriter sw = new StreamWriter("Temp.txt", true);
       sw.WriteLine($"Time:{DateTime.Now.ToLongTimeString()}");
       sw.Close();
   }
   catch (IOException e)
   {
       Console.WriteLine("An IO Exception :");
       Console.WriteLine(e.ToString());
       Console.ReadKey();
       return;
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# StreamReader对象

StreamReader对象的创建方式跟StreamWriter很类似:

static void Main(string[] args)
{
   try
   {
       //第一种方法从FileStream中创建
       //FileStream FStream = new FileStream("Temp.txt", FileMode.Open);
       //StreamReader sr = new StreamReader(FStream);

       //第二种方法直接从StreamReader创建
       StreamReader sr = new StreamReader("Temp.txt");
       string line = sr.ReadLine();
       int lineNum = 1;
       while(line != null)
       {
           Console.WriteLine($"lineNum {lineNum++}:"+line);
           line = sr.ReadLine();
       }
       sr.Close();
   }
   catch (IOException e)
   {
       Console.WriteLine("An IO Exception :");
       Console.WriteLine(e.ToString());
       Console.ReadKey();
       return;
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 异步文件访问

有时要一次性的执行大量文件访问操作,或者要处理的文件很大,读写文件系统很缓慢,这个时候你希望在执行这些操作的时候你能做其他操作,这对桌面应用尤其重要,对用户保持良好的响应性。为了支持这种异步操作 .Net 4.5引入一些异步方式来操作流,可以从FileStream,StreamReader,StreamWriter找到带有Async后缀的方法,比如ReadLineAsync()方法。

# 读写压缩文件

在处理文件时,常会占用大量硬盘空间,尤其是图形和声音文件,这个时候就希望有压缩文件和解压缩文件的工具,System.IO.Compression名称空间包含这些类,可使用GZIP算法或Deflate算法。来看下实例:

class Program
{
  static void SaveCompressFile(string filename,string data)
  {
      FileStream filestream = new FileStream(filename, FileMode.Create, FileAccess.Write);
      GZipStream compressStream = new GZipStream(filestream, CompressionMode.Compress);
      StreamWriter sw = new StreamWriter(compressStream);
      sw.Write(data);
      sw.Close();
  }

  static string LoadCompressFile(string filename)
  {
      FileStream filestream = new FileStream(filename, FileMode.Open, FileAccess.Read);
      GZipStream decompressStream = new GZipStream(filestream, CompressionMode.Decompress);
      StreamReader sr = new StreamReader(decompressStream);
      string data = sr.ReadToEnd();
      sr.Close();
      return data;
  }
  static void Main(string[] args)
  {
      try
      {
          string fileName = "CompressFile.txt";
          Console.WriteLine("Please Enter a sentence,it will be repeated constructed:");
          string sourceStr = Console.ReadLine();
          StringBuilder savedStr = new StringBuilder(sourceStr.Length * 100);
          for (int i = 0; i < 100; i++)
          {
              savedStr.Append(sourceStr);
          }
          sourceStr = savedStr.ToString();
          Console.WriteLine("Before compress size:" + sourceStr.Length);
          SaveCompressFile(fileName, sourceStr);
          Console.WriteLine("Into file size:" + (new FileInfo(fileName)).Length);
          string loadStr = LoadCompressFile(fileName);
          Console.WriteLine("Load file content:"+loadStr.Substring(0,loadStr.Length/100));
      }catch(IOException ex)
      {
          Console.WriteLine("An IO exception :");
          Console.WriteLine(ex.ToString());
          Console.ReadKey();
      }

  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 监控文件系统

当我们需要监控文件系统时就需要使用FileSystemWatcher类,这个类提供了几个事件,要使用它需要设置一些属性,比如监控的位置,内容以及触发事件时应调用的事件处理程序。

属性 说明
Path 要监控文件的位置或目录
NotifyFilter NotifyFilter枚举的组合,它指定了监控被监控文件的哪些内容,这些表示要监控的文件或文件夹得属性,如果这些属性发生了变化,就引发事件,比如Attributes,CreationTime,DirectoryName,FileName,LastAccess,LastWrite,Security和Size。
Filter 过滤器指定要监控哪些文件。

设置完上面的属性,还需要为Changed,Created,Deleted和Renamed等4个事件编写事件处理程序。做好这些之后,设置EnableRaisingEvents为true,就可以开始监控工作。

class Program
{
  static void Main(string[] args)
  {
      FileSystemWatcher watcher = new FileSystemWatcher();
      Console.WriteLine("Enter A Path:");
      watcher.Path = Console.ReadLine();
      //watcher.Filter = ""
      watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size;
      watcher.Deleted += (s, e) => Console.WriteLine($"Deleted:{e.FullPath} deleted!");
      watcher.Renamed += (s, e) => Console.WriteLine($"Renamed:{e.OldName} changed to {e.Name}");
      watcher.Changed += (s, e) => Console.WriteLine($"Changed:{e.FullPath} {e.ChangeType.ToString()}");
      watcher.Created += (s, e) => Console.WriteLine($"Created:{e.FullPath} created!");
      watcher.EnableRaisingEvents = true;
      Console.ReadKey();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17