C#笔记——文件和流 I/O

本文最后更新于:2023年12月3日 晚上

什么是文件和流 I/O

文件是一个由字节组成的有序的命名集合,而流则是一个字节序列。文件和流(的) I/O 是指在存储媒介中传入或传出数据。
而在 .NET 中,System.IO命名空间包含允许以异步方式和同步方式对数据流和文件进行读取和写入操作的类型。下面就先来介绍一下这个命名空间中的各种东西。

文件和目录

下面是一些常用的文件和目录类:

  • File:提供用于创建、复制、删除、移动和打开文件的静态方法,并可帮助创建FileStream对象。
  • FileInfo:提供用于创建、复制、删除、移动和打开文件的实例方法,并可帮助创建FileStream对象。
  • Direction:提供用于创建、移动和枚举目录和子目录的静态方法。
  • DirectionInfo:提供用于创建、移动和枚举目录和子目录的实例方法。
  • Path:提供用于以跨平台的方式处理目录字符串的方法和属性。
    很直白啊,带有File的就是处理文件的类,带有Direction的就是处理目录的类,默认都提供的是静态方法,类中带Info的则提供实例方法。

下面是一些常用的流类:

  • FileStream:用于对文件进行读取和写入操作。
  • IsolatedStorageFileStream:用于对独立存储中的文件进行读取和写入操作。不熟。
  • MemoryStream:用于作为后备存储对内存进行读取和写入操作。不熟。
  • BufferedStream:用于改进读取和写入操作的性能。不熟。
  • NetworkStream:用于通过网络套接字进行读取和写入。不熟。
  • PipeStream:用于通过匿名和命名管道进行读取和写入。不熟。
  • CryptoStream:用于将数据流链接到加密转换。不熟。
    一圈下来就看懂第一个。

读取器和编写器

通常,流用于字节输入和输出。读取器和编写器则是处理编码字符与字节之间的来回转换,以便流可以完成操作。下面是一些常用的读取器和编写器类:

  • BinaryReaderBinaryWriter:用于将基元数据类型作为二进制值进行读取和写入。
  • StreamReaderStreamWriter:用于通过使用编码值在字符和字节之间来回转换来读取和写入字符。
  • StringReaderStringWriter:用于从字符串读取字符以及将字符写入字符串中。
  • TextReaderTextWriter:用作其他读取器和编写器(读取和写入字符和字符串,而不是二进制数据)的抽象基类。
    其实也很好懂,啊,就是你要把一个字符串写入文件里边,啊,你还得搞个这个东西帮助你进行编码转换,啊。哪来的狗,guna.jpg

压缩

用来对文件和流进行压缩或解压缩的类(这你都有?)。下面是一些常用类:

  • ZipArchive:用于在 zip 存档中创建和检索条目。
  • ZipArchiveEntry:用于表示压缩文件。
  • ZipFile:用于创建、提取和打开压缩包。
  • ZipFileExtensions:用于创建和提供压缩包中的条目。
  • DeflateStream: 用于使用 Deflate 算法对流进行压缩和解压缩。
  • GZipStream:用于采用 gzip 数据格式对流进行压缩和解压缩。

独立存储

真不熟家人们,先不管这个东西了。

Windows 系统中的文件路径格式

想要准确地操作文件,那肯定得先明白对应操作系统中地文件路径格式啦!

传统 DOS 路径

标准的 DOS 路径可由以下三部分组成:

  • 卷号或驱动器号,后跟卷分隔符(:)。例如:C:
  • 目录名称。目录分隔符(\)用来分隔嵌套目录层次结构中的子目录。例如:\Program Files\Tencent
  • 可选的文件名。目录分隔符(\)用来分隔文件路径和文件名。例如:\Tencent\QQ.exe
    路径分为绝对路径和相对路径,前者指定了卷号或驱动器号,后者则没有。而后者的开头若为目录分隔符,则路径相对于当前驱动器根路径(\Program Files\Tencent),否则相对于当前目录(Program Files\Tencent)。

    相对路径在多线程应用程序(也就是大多数应用程序)中很危险,因为当前目录是分进程的设置。

    好了,了解传统路径一般情况下够用了,至于其他的 UNC 路径、DOS 设备路径可以自行去文档看看。(什么?你说要给其他环境做适配?不会!.jpg)

通用 I/O 任务

如何复制目录

只记录一点关键步骤。首先获取源目录的信息,即创建一个源目录的DirectoryInfo对象:var dir = new DirectoryInfo(sourceDir)。得到源目录下的所有文件:dir.GetFiles(),该方法返回FileInfo[]类型。复制文件:file.CopyTo(targetFilePath),这是FileInfo类的实例方法。如果要合并两个路径的话,可以使用Path.Combine方法,该方法接受两个字符串参数并将它们合并成一个路径(字符串)。
如果要将该目录及子目录下的所有文件都复制一遍,可以用递归。
代码如下:

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
internal class HowToCopyDir
{
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
{
// 获得源目录的信息
var dir = new DirectoryInfo(sourceDir);

// 检查源目录是否存在
if (!dir.Exists)
{
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
}
else
{
Console.WriteLine($"Start to copy dir: {dir.FullName}");
}

// 在开始复制之前缓存目录
DirectoryInfo[] dirs = dir.GetDirectories();

// 创建目标目录
Directory.CreateDirectory(destinationDir);

// 获得源目录下的所有文件,并将其复制到目标目录
foreach (FileInfo file in dir.GetFiles())
{
string targetFilePath = Path.Combine(destinationDir, file.Name);
file.CopyTo(targetFilePath);
Console.WriteLine($"Copy file: {file.Name}");
}

// 如果允许递归,则进行递归复制,将其子目录的文件也一同复制
if (recursive)
{
foreach (DirectoryInfo subDir in dirs)
{
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
CopyDirectory(subDir.FullName, newDestinationDir, true);
}
}
}
}

如何枚举目录和文件

下面是几个返回可枚举的文件和目录集合的常用方法:

搜索并返回 使用方法
目录名称 Directory.EnumerateDirectories
目录信息(DirectoryInfo DirectoryInfo.EnumerateDirectories
文件名称 Directory.EnumerateFiles
文件信息(FileInfo DirectoryInfo.EnumerateFiles

可以看到方法名简明扼要啊,点个赞先。至于剩下的文件系统条目,不熟,所以没写了。(但其实偷偷看了一眼文档,FileSystemInfo类其实是FileInfoDirectoryInfo类的基类!)
示例代码没有,自行查阅官方文档

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

这一节其实是讲如何使用BinaryReaderBinaryWriter类来写入和读取字符串以外的数据的。这里就稍微根据文档里的示例代码来总结一下。
如果要写入数据,先打开(创建)一个文件流(FileStream),然后根据这个文件流来创建一个编写器(BinaryWriter),调用这个编写器的Write方法来写入数据,比如可以直接写入一个int值:w.Write(1)。如果要读取数据,同样地先打开一个FileStream流,然后根据这个文件流来创建BinaryReader读取器,用该类的实例方法来读取文件数据,例如:r.ReadInt32()

如何打开并追加文本到日志文件

注意这里是追加文本,也就是不会覆盖或修改文件中原有的文本。通过File.AppendText方法来获得一个StreamWriter对象,然后调用StreamWriter类型的WriteWriteLine方法即可追加文本。

如果需要换行并将光标置于行首,应该向流写入\r\n

我们也可以用另一种方法来追加文本,那就是将信息存储为一个字符串或字符串数组,并使用File.WriteAllTextFile.WriteAllLines方法追加文本。

如何将文本写入文件

这一章节介绍的是StreamWriter的使用方法,包括:同步写入文本、同步追加文本(上一节已经讲过)、异步写入文本。另外也讲到了使用文件类编写和追加文本。

  • StreamWriter提供:Write/WriteLine/WriteAsync/WriteAsync方法。
  • File提供:WriteAllLines/WriteAllText/AppendAllLines/AppendAllText/AppendText静态方法。

如果是写入文本,那么就这样创建StreamWriter对象:using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt")))
如果是追加文本,那么就这样创建StreamWriter对象:using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt"), true))

异步文件访问

如果不需要对文件 I/O 进行精细的控制,那么直接参照下面使用File.WriteAllTextAsyncFile.ReadAllTextAsync即可。如果需要,请使用FileStream类。

写入文本

1
2
3
4
5
6
7
public async Task SimpleWriteAsync()
{
string filePath = "simple.txt";
string text = $"Hello World";

await File.WriteAllTextAsync(filePath, text2);
}

读取文本

1
2
3
4
5
6
7
public async Task SimpleReadAsync()
{
string filePath = "simple.txt";
string text = await File.ReadAllTextAsync(filePath);

Console.WriteLine(text);
}

并行异步 I/O

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public async Task SimpleParallelWriteAsync()
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();

for (int index = 11; index <= 20; index++)
{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";
string text = $"In file {index}{Environment.NewLine}";

writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}

await Task.WhenAll(writeTaskList);
}

这里有一只爱丽丝

希望本文章能够帮到您~


C#笔记——文件和流 I/O
https://map1e-g.github.io/2023/11/07/CSharp-learning-11/
作者
MaP1e-G
发布于
2023年11月7日
许可协议