目录
七、 异常接口(IDisposable interface)
十六、生成和转移create, move directory
本文专门介绍 C# 中的输入和输出。 C# 中的输入和输出基于流。文件流、文件建立修改等属性操作,文件目录操作等。
流是字节序列的抽象,例如文件、输入/输出设备、进程间通信管道或 TCP/IP 套接字。流将数据从一个点传输到另一个点。流也能够操纵数据;例如,他们可以压缩或加密数据。在 .NET Framework 中,System.IO 命名空间包含能够读取和写入数据流和文件的类型。
C# 在 File 类中为 I/O 操作提供高级方法,在 StreamReader 或 StreamWriter 等类中提供低级方法。
I/O 操作容易出错。我们可能会遇到异常,例如 FileNotFoundException 或 UnauthorizedAccessException。与 Java 不同,C# 不强制程序员手动处理异常。是否手动处理异常由程序员决定。如果异常没有在 try/catch/finally 结构中手动处理,则由 CLR 处理异常。
必须释放 I/O 资源。可以使用 Dispose 方法在 finally 子句中手动释放资源。 using 关键字可用于自动释放资源。此外,File 类中的方法会为我们释放资源。
在示例中,我们使用这个简单的文本文件:
thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.ssan
File 为单个文件的创建、复制、删除、移动和打开提供静态方法,并有助于创建 FileStream 对象。
Note: The File.ReadAllText
is not suited for reading very large files.
File.ReadAllText 打开一个文件,读取文件中具有指定编码的所有文本,然后关闭文件。
Program.cs
- using System.Text;
-
- var path = "/home/janbodnar/Documents/thermopylae.txt";
-
- var text = File.ReadAllText(path, Encoding.UTF8);
- Console.WriteLine(text);
程序读取thermopylae.txt 文件的内容并将它们打印到控制台。
var text = File.ReadAllText(path, Encoding.UTF8);
我们一口气将整个文件读入一个字符串。在第二个参数中,我们指定编码。
$ dotnet run The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
File.ReadAllLines 打开一个文本文件,将文件的所有行读入一个字符串数组,然后关闭文件。 File.ReadAllLines 是一种在 C# 中读取文件的便捷方法。处理非常大的文件时不应使用它。
Program.cs
- var path = "/home/janbodnar/Documents/thermopylae.txt";
-
- string[] lines = File.ReadAllLines(path);
-
- foreach (string line in lines)
- {
- Console.WriteLine(line);
- }
该示例将文件中的所有行读取到字符串数组中。我们在 foreach 循环中遍历数组并将每一行打印到控制台。
File.CreateText 创建或打开用于写入 UTF-8 编码文本的文件。如果文件已经存在,则覆盖其内容。
Program.cs
- var path = "/home/janbodnar/Documents/cars.txt";
-
- using var sw = File.CreateText(path);
-
- sw.WriteLine("Hummer");
- sw.WriteLine("Skoda");
- sw.WriteLine("BMW");
- sw.WriteLine("Volkswagen");
- sw.WriteLine("Volvo");
在示例中,我们创建了一个 cars.txt 文件并将一些汽车名称写入其中。
using var sw = File.CreateText(path);
CreateText 方法创建或打开一个用于写入 UTF-8 编码文本的文件。它返回一个 StreamWriter 对象。
sw.WriteLine("Hummer"); sw.WriteLine("Skoda"); ...
我们向流中写入两行。
通过 File 类,我们可以获得文件的创建、最后写入和最后访问时间。 Exists 方法确定指定文件是否存在。
Program.cs
- var path = "cars.txt";
-
- if (File.Exists(path))
- {
- Console.WriteLine(File.GetCreationTime(path));
- Console.WriteLine(File.GetLastWriteTime(path));
- Console.WriteLine(File.GetLastAccessTime(path));
- }
如果指定的文件存在,我们确定它的创建、最后写入和最后访问时间。
if (File.Exists(path))
如果调用者具有所需的权限并且路径包含现有文件的名称,Exists 方法将返回 true;否则,错误。如果 path 为 null、无效路径或长度为零的字符串,此方法也会返回 false。
Console.WriteLine(File.GetCreationTime(path)); Console.WriteLine(File.GetLastWriteTime(path)); Console.WriteLine(File.GetLastAccessTime(path));
我们得到指定文件的创建时间、最后写入时间和最后访问时间。
$ dotnet run 2/25/2021 11:41:19 AM 2/25/2021 11:41:19 AM 2/25/2021 11:41:27 AM
File.Copy 方法将现有文件复制到新文件。它允许覆盖同名文件。
Program.cs
- var srcPath = "/home/janbodnar/Documents/cars.txt";
- var destPath = "/home/janbodnar/Documents/cars2.txt";
-
- File.Copy(srcPath, destPath, true);
-
- Console.WriteLine("File copied");
然后我们将文件的内容复制到另一个文件中。
var srcPath = "/home/janbodnar/Documents/cars.txt"; var destPath = "/home/janbodnar/Documents/cars2.txt";
File.Copy(srcPath, destPath, true);
Copy 方法复制文件。第三个参数指定文件是否应该被覆盖,如果它存在的话。
流实现了 IDisposable 接口。实现此接口的对象必须尽早手动处置。这是通过在 finally 块中调用 Dispose 方法或使用 using 语句来完成的。
Program.cs
- StreamReader? sr = null;
-
- var path = "thermopylae.txt";
-
- try
- {
- sr = new StreamReader(path);
- Console.WriteLine(sr.ReadToEnd());
- }
- catch (IOException e)
- {
- Console.WriteLine("Cannot read file");
- Console.WriteLine(e.Message);
- }
- catch (UnauthorizedAccessException e)
- {
- Console.WriteLine("Cannot access file");
- Console.WriteLine(e.Message);
- }
- finally
- {
- sr?.Dispose();
- }
在此示例中,我们从磁盘上的文件中读取字符。我们手动释放分配的资源。
- sr = new StreamReader(path);
- Console.WriteLine(sr.ReadToEnd());
StreamReader 类用于读取字符。它的父级实现了 IDisposable 接口。
- } catch (IOException e)
- {
- Console.WriteLine("Cannot read file");
- Console.WriteLine(e.Message);
-
- } catch (UnauthorizedAccessException e)
- {
- Console.WriteLine("Cannot access file");
- Console.WriteLine(e.Message);
- }
可能的异常在 catch 块中处理。
finally { sr?.Dispose(); }
在 finally 块中,Dispose 方法清理资源。使用空条件运算符,我们仅在变量不为空时才调用该方法。
using 语句定义了一个范围,对象将在该范围结束。它提供了一种方便的语法,可确保正确使用 IDisposable 对象。
Program.cs
var path = "/home/janbodnar/Documents/thermopylae.txt"; using (var sr = new StreamReader(path)) { Console.WriteLine(sr.ReadToEnd()); }
该示例读取thermopylae.txt 文件的内容。使用 using 语句释放资源。如果我们不处理 IO 异常,它们将由 CLR 处理。
using 声明是一个变量声明,前面有 using 关键字。它告诉编译器被声明的变量应该放在封闭范围的末尾。 using 声明自 C# 8.0 起可用。
Program.cs
- var path = "thermopylae.txt";
- using var sr = new StreamReader(path);
- Console.WriteLine(sr.ReadToEnd());
该示例读取thermopylae.txt 文件的内容。当 sr 变量超出范围时(在 Main 方法结束时),资源会自动清理。
MemoryStream 是一种处理计算机内存中数据的流。
Program.cs
- using var ms = new MemoryStream(6);
- ms.WriteByte(9);
- ms.WriteByte(11);
- ms.WriteByte(6);
- ms.WriteByte(8);
- ms.WriteByte(3);
- ms.WriteByte(7);
- ms.Position = 0;
- int rs = ms.ReadByte();
- do
- {
- Console.WriteLine(rs);
- rs = ms.ReadByte();
- } while (rs != -1);
我们使用 MemoryStream 将六个数字写入内存。然后我们读取这些数字并将它们打印到控制台。
using var ms = new MemoryStream(6);
该行创建并初始化了一个容量为 6 个字节的 MemoryStream 对象。
ms.WriteByte(9); ms.WriteByte(11); ms.WriteByte(6); ...
WriteByte 方法在当前位置将一个字节写入当前流。
ms.Position = 0;
我们使用 Position 属性将光标在流中的位置设置为开头。
- do
- {
- Console.WriteLine(rs);
- rs = ms.ReadByte();
-
- } while (rs != -1);
在这里,我们从流中读取所有字节并将它们打印到控制台。
$ dotnet run
9
11
6
8
3
7s
StreamReader 从字节流中读取字符。它默认为 UTF-8 编码。
Program.cs
- var path = "/home/janbodnar/Documents/thermopylae.txt";
-
- using var sr = new StreamReader(path);
-
- while (sr.Peek() >= 0)
- {
- Console.WriteLine(sr.ReadLine());
- }
我们读取文件的内容。这次我们使用 ReadLine 方法逐行读取文件。
while (sr.Peek() >= 0) { Console.WriteLine(sr.ReadLine()); }
Peek 方法返回下一个可用字符,但不使用它。它指示我们是否可以再次调用 ReadLine 方法。如果没有要读取的字符,则返回 -1。
在下一个示例中,我们将计算行数。
Program.cs
int count = 0; var path = "/home/janbodnar/Documents/thermopylae.txt"; using var sr = new StreamReader(path); while (sr.ReadLine() != null) { count++; } Console.WriteLine($"There are {count} lines");
We use a StreamReader
and a while loop.
while(stream.ReadLine() != null) { count++; }
在 while 循环中,我们使用 ReadLine 方法从流中读取一行。它从流中返回一行,如果到达输入流的末尾,则返回 null。
$ dotnet run
There are 3 linessshi
StreamWriter 以特定编码将字符写入流。
Program.cs
var path = "/home/janbodnar/Documents/newfile.txt"; using var sw = new StreamWriter(path); sw.WriteLine("Today is a beautiful day.");
该示例使用 StreamWriter 将字符串写入文件。
using var sw = new StreamWriter(path);
我们创建了一个新的 StreamWriter。默认编码为 UTF-8。 StreamWriter 将路径作为参数。如果文件存在,则覆盖;否则,将创建一个新文件。
FileStream 为文件提供流,支持同步和异步读写操作。StreamReader 和 StreamWriter 处理文本数据,而 FileStream 处理字节。
Program.cs
- using System.Text;
-
- var path = "/home/janbodnar/Documents/newfile2.txt";
-
- using var fs = new FileStream(path, FileMode.Append);
-
- var text = "Фёдор Михайлович Достоевский\n";
- byte[] bytes = new UTF8Encoding().GetBytes(text);
-
- fs.Write(bytes, 0, bytes.Length);
我们将一些俄语西里尔文文本写入文件。
using .Text;
UTF8Encoding 类位于 System.Text 命名空间中。
using var fs = new FileStream(path, FileMode.Append);
一个 FileStream 对象被创建。第二个参数是打开文件的模式。如果文件存在,追加模式将打开文件并查找文件末尾,或创建一个新文件。
var text = "Фёдор Михайлович Достоевский";
这是俄语西里尔文文本。
byte[] bytes = new UTF8Encoding().GetBytes(text);
从俄语西里尔文文本创建一个字节数组。
fs.Write(bytes, 0, bytes.Length);
我们将字节写入文件流。
$ cat /home/janbodnar/Documents/newfile2.txt Фёдор Михайлович Достоевский
我们显示创建的文件的内容。
我们可以使用流来读取 XML 数据。 XmlTextReader 是在 C# 中读取 XML 文件的类。该类是只进的和只读的。
我们有以下 XML 测试文件:
languages.xml
- "1.0" encoding="utf-8" ?>
-
Python -
Ruby -
Javascript -
C#
此文件包含自定义 XML 标记之间的语言名称。
Program.cs
- using System.Xml;
-
- string path = "/home/janbodnar/Documents/languages.xml";
- using var xreader = new XmlTextReader(path);
-
- xreader.MoveToContent();
-
- while (xreader.Read())
- {
- var node = xreader.NodeType switch
- {
- XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
- XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
- _ => ""
- };
-
- Console.Write(node);
- }
此示例从 XML 文件中读取数据并将其打印到终端。
using System.Xml;
System.Xml 命名空间包含与 Xml 读写相关的类。
using var xreader = new XmlTextReader(path);
创建一个 XmlTextReader 对象。它是一种提供对 XML 数据的快速、非缓存、只进的访问的阅读器。它将文件名作为参数。
xreader.MoveToContent();
MoveToContent 方法移动到 XML 文件的实际内容。
while (xreader.Read())
此行从流中读取下一个节点。如果没有更多节点,Read 方法将返回 false。
var node = xreader.NodeType switch { XmlNodeType.Element => String.Format("{0}: ", xreader.Name), XmlNodeType.Text => String.Format("{0} \n", xreader.Value), _ => "" }; Console.Write(node);
这里我们打印元素名称和元素文本。
$ dotnet run language: Python language: Ruby language: Javascript language: C#
System.IO.Directory 是一个类,它具有用于创建、移动和枚举目录和子目录的静态方法。
Program.cs
Directory.CreateDirectory("temp"); Directory.CreateDirectory("newdir"); Directory.Move("temp", "temporary");
我们创建两个目录并重命名创建的目录之一。目录是在项目文件夹中创建的。
Directory.CreateDirectory("temp");
CreateDirectory 方法创建一个新目录。
Directory.Move("temp", "temporary"); Move 方法为指定的目录提供了一个新名称。
DirectoryInfo 公开了用于创建、移动和枚举目录和子目录的实例方法。
Program.cs
- var path = "/home/janbodnar/Documents";
- var dirInfo = new DirectoryInfo(path);
-
- string[] files = Directory.GetFiles(path);
- DirectoryInfo[] dirs = dirInfo.GetDirectories();
-
- foreach (DirectoryInfo subDir in dirs)
- {
- Console.WriteLine(subDir.Name);
- }
-
- foreach (string fileName in files)
- {
- Console.WriteLine(fileName);
- }
我们使用 DirectoryInfo 类遍历特定目录并打印其内容。
var path = "/home/janbodnar/Documents"; var DirInfo = new DirectoryInfo(path);
我们显示指定目录的内容。
string[] files = Directory.GetFiles(path);
我们使用静态 GetFiles 方法获取目录的所有文件。
DirectoryInfo[] dirs = dir.GetDirectories();
我们得到所有的目录。
foreach (DirectoryInfo subDir in dirs) { Console.WriteLine(subDir.Name); }
在这里,我们遍历目录并将它们的名称打印到控制台。
foreach (string fileName in files) { Console.WriteLine(fileName); }
在这里,我们遍历文件数组并将它们的名称打印到控制台。
在本章中,我们介绍了 C# 中的输入/输出操作。