🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# C# 输入和输出 > 原文: [https://zetcode.com/lang/csharp/io/](https://zetcode.com/lang/csharp/io/) 本章专门介绍 C# 中的输入和输出。 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` ```cs 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. ``` ## C# `File.ReadAllText` `File`提供了用于创建,复制,删除,移动和打开单个文件的静态方法,并有助于创建`FileStream`对象。 > **注意**:`File.ReadAllText()`不适合读取非常大的文件。 `File.ReadAllText()`打开一个文件,以指定的编码读取文件中的所有文本,然后关闭该文件。 `Program.cs` ```cs using System; using System.IO; using System.Text; namespace ReadFileIntoString { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\thermopylae.txt"; var text = File.ReadAllText(path, Encoding.UTF8); Console.WriteLine(text); } } } ``` 该程序读取`thermopylae.txt`文件的内容,并将其打印到控制台。 ```cs var text = File.ReadAllText(path, Encoding.UTF8); ``` 我们一次性将整个文件读成字符串。 在第二个参数中,我们指定编码。 ```cs $ 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. ``` 这是输出。 ## C# `File.ReadAllLines` `File.ReadAllLines()`打开一个文本文件,将文件的所有行读入字符串数组,然后关闭文件。 `File.ReadAllLines()`是一种使用 C# 读取文件的便捷方法。 处理非常大的文件时,不应使用它。 `Program.cs` ```cs using System; using System.IO; namespace ReadAllLines { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\thermopylae.txt"; string[] lines = File.ReadAllLines(path); foreach (string line in lines) { Console.WriteLine(line); } } } } ``` 该示例将文件中的所有行读入字符串数组。 我们在`foreach`循环中遍历数组,并将每一行打印到控制台。 ## C# 创建文件 `File.CreateText()`创建或打开用于写入 UTF-8 编码文本的文件。 如果文件已经存在,则其内容将被覆盖。 `Program.cs` ```cs using System; using System.IO; namespace CreateFileEx { class Program { static void Main() { var path = @"C:\Users\Jano\Documents\cars.txt"; using (StreamWriter sw = File.CreateText(path)) { sw.WriteLine("Hummer"); sw.WriteLine("Skoda"); sw.WriteLine("BMW"); sw.WriteLine("Volkswagen"); sw.WriteLine("Volvo"); } } } } ``` 在示例中,我们创建一个`cars.txt`文件,并将一些汽车名称写入其中。 ```cs using (StreamWriter sw = File.CreateText(path)) ``` `CreateText()`方法创建或打开一个文件,用于写入 UTF-8 编码的文本。 它返回一个`StreamWriter`对象。 ```cs sw.WriteLine("Hummer"); sw.WriteLine("Skoda"); ... ``` 我们向流写入两行。 ```cs $ cat C:\Users\Jano\Documents\cars.txt Hummer Skoda BMW Volkswagen Volvo ``` 我们已成功将五个汽车名称写入文件。 ## C# 创建,最后写入,最后访问时间 使用`File`类,我们可以获取文件的创建,最后写入和最后访问时间。 `Exists()`方法确定指定的文件是否存在。 `Program.cs` ```cs using System; using System.IO; namespace FileTimes { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\cars.txt"; if (File.Exists(path)) { Console.WriteLine(File.GetCreationTime(path)); Console.WriteLine(File.GetLastWriteTime(path)); Console.WriteLine(File.GetLastAccessTime(path)); } } } } ``` 如果存在指定的文件,我们将确定其创建,最后写入和最后访问时间。 ```cs if (File.Exists(path)) ``` 如果调用方具有所需的权限并且路径包含现有文件的名称,则`Exists()`方法返回`true`。 否则为`false`。 如果`path`为`null`,无效路径或长度为零的字符串,则此方法还返回`false`。 ```cs Console.WriteLine(File.GetCreationTime(path)); Console.WriteLine(File.GetLastWriteTime(path)); Console.WriteLine(File.GetLastAccessTime(path)); ``` 我们得到指定文件的创建时间,上次写入时间和上次访问时间。 ```cs $ dotnet run 10/13/2019 1:59:03 PM 10/13/2019 1:59:03 PM 10/13/2019 1:59:03 PM ``` 这是一个示例输出。 ## C# 复制文件 `File.Copy()`方法将现有文件复制到新文件。 它允许覆盖同名文件。 `Program.cs` ```cs using System; using System.IO; namespace CopyFileEx { class Program { static void Main(string[] args) { var srcPath = @"C:\Users\Jano\Documents\cars.txt"; var destPath = @"C:\Users\Jano\Documents\cars2.txt"; File.Copy(srcPath, destPath, true); Console.WriteLine("File copied"); } } } ``` 然后,我们将文件的内容复制到另一个文件。 ```cs var srcPath = @"C:\Users\Jano\Documents\cars.txt"; var destPath = @"C:\Users\Jano\Documents\cars2.txt"; ``` 这是源文件和目标文件。 ```cs File.Copy(srcPath, destPath, true); ``` `Copy()`方法复制文件。 第三个参数指定是否应覆盖文件(如果存在)。 ```cs $ dotnet run File copied ``` This is a sample output. ## C# `IDisposable`接口 流实现`IDisposable`接口。 实现此接口的对象必须尽早手动处理。 这是通过在`finally`块中调用`Dispose()`方法或利用`using`语句来完成的。 `Program.cs` ```cs using System; using System.IO; namespace ManualRelease { class Program { static void Main(string[] args) { StreamReader sr = null; var path = @"C:\Users\Jano\Documents\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(); } } } } ``` 在此示例中,我们从磁盘上的文件读取字符。 我们手动释放分配的资源。 ```cs sr = new StreamReader(path); Console.WriteLine(sr.ReadToEnd()); ``` `StreamReader`类用于读取字符。 其父级实现`IDisposable`接口。 ```cs } 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`块中处理。 ```cs finally { sr?.Dispose(); } ``` 在`finally`块中,`Dispose()`方法清理资源。 使用空条件运算符时,仅当变量不是`null`时,才调用该方法。 ## C# 使用语句 `using`语句定义一个范围,在该范围的末尾将放置一个对象。 它提供了一种方便的语法,可确保正确使用`IDisposable`对象。 `Program.cs` ```cs using System; using System.IO; namespace AutomaticCleanup { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\thermopylae.txt"; using (var sr = new StreamReader(path)) { Console.WriteLine(sr.ReadToEnd()); } } } } ``` 该示例读取`thermopylae.txt`文件的内容。 资源通过`using`语句释放。 如果我们不处理 IO 异常,则 CLR 将处理它们。 ## C# `using`声明 `using`声明是在`using`关键字之后的变量声明。 它告诉编译器声明的变量应放在封闭范围的末尾。 从 C# 8.0 开始,`using`声明可用。 `Program.cs` ```cs using System; using System.IO; namespace UsingDeclaration { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\thermopylae.txt"; using var sr = new StreamReader(path); Console.WriteLine(sr.ReadToEnd()); } } } ``` 该示例读取`thermopylae.txt`文件的内容。 当`sr`变量超出范围时(在`Main()`方法的末尾),将自动清除资源。 ## C# `MemoryStream` `MemoryStream`是用于处理计算机内存中数据的流。 `Program.cs` ```cs using System; using System.IO; namespace MemoryStreamEx { class Program { static void Main(string[] args) { 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`将六个数字写入存储器。 然后,我们读取这些数字并将其打印到控制台。 ```cs using var ms = new MemoryStream(6); ``` 该行创建并初始化一个容量为六个字节的`MemoryStream`对象。 ```cs ms.WriteByte(9); ms.WriteByte(11); ms.WriteByte(6); ... ``` `WriteByte()`方法在当前位置的当前流中写入一个字节。 ```cs ms.Position = 0; ``` 我们使用`Position`属性将光标在流中的位置设置为开头。 ```cs do { Console.WriteLine(rs); rs = ms.ReadByte(); } while (rs != -1); ``` 在这里,我们从流中读取所有字节并将其打印到控制台。 ```cs $ dotnet run 9 11 6 8 3 7 ``` 这是示例的输出。 ## C# `StreamReader` `StreamReader`从字节流中读取字符。 默认为 UTF-8 编码。 `Program.cs` ```cs using System; using System.IO; namespace ReadFileEx { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\thermopylae.txt"; using var sr = new StreamReader(path); while (sr.Peek() >= 0) { Console.WriteLine(sr.ReadLine()); } } } } ``` 我们读取文件的内容。 这次我们使用`ReadLine()`方法逐行读取文件。 ```cs while (sr.Peek() >= 0) { Console.WriteLine(sr.ReadLine()); } ``` `Peek()`方法返回下一个可用字符,但不使用它。 它指示我们是否可以再次调用`ReadLine()`方法。 如果没有要读取的字符,则返回`-1`。 ## C# 计数行 在下一个示例中,我们将对行进行计数。 `Program.cs` ```cs using System; using System.IO; namespace CountingLines { class Program { static void Main(string[] args) { int count = 0; var path = @"C:\Users\Jano\Documents\thermopylae.txt"; using var sr = new StreamReader(path); while (sr.ReadLine() != null) { count++; } Console.WriteLine("There are {0} lines", count); } } } ``` 我们正在计算文件中的行数。 ```cs while(stream.ReadLine() != null) { count++; } ``` 在`while`循环中,我们使用`ReadLine()`方法从流中读取一行。 如果到达输入流的末尾,它将从流或`null`中返回一行。 ```cs $ dotnet run There are 3 lines ``` 该文件有三行。 ## C# `StreamWriter` `StreamWriter`以特定编码将字符写入流。 `Program.cs` ```cs using System; using System.IO; namespace WriteToFile { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\Documents\newfile.txt"; using var sw = new StreamWriter(path); sw.WriteLine("Today is a beautiful day."); } } } ``` 该示例使用`StreamWriter`将字符串写入文件。 ```cs using (var sw = new StreamWriter(path)) ``` 我们创建一个新的`StreamWriter`。 默认值是 UTF-8。 `StreamWriter`将路径作为参数。 如果文件存在,它将被覆盖; 否则,将创建一个新文件。 ```cs $ dotnet run $ cat C:\Users\Jano\Documents\newfile.txt Today is a beautiful day. ``` 我们已经使用`cat`命令显示了文件的内容。 ## C# `FileStream` `FileStream`为文件提供流,同时支持同步和异步读取和写入操作。 `StreamReader`和`StreamWriter`处理文本数据,而`FileStream`处理字节。 `Program.cs` ```cs using System.IO; using System.Text; namespace FileStreamEx { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\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); } } } ``` 我们用俄语西里尔字母写一些文本到文件中。 ```cs using System.Text; ``` `UTF8Encoding`类位于`System.Text`命名空间中。 ```cs using var fs = new FileStream(path, FileMode.Append); ``` 创建一个`FileStream`对象。 第二个参数是打开文件的模式。 附加模式将打开文件(如果存在)并查找到文件末尾,或创建一个新文件。 ```cs var text = "Фёдор Михайлович Достоевский"; ``` 这是俄文西里尔文的文字。 ```cs byte[] bytes = new UTF8Encoding().GetBytes(text); ``` 从俄语西里尔字母文本创建一个字节数组。 ```cs fs.Write(bytes, 0, bytes.Length); ``` 我们将字节写入文件流。 ```cs $ cat C:\Users\Jano\Documents\newfile2.txt Фёдор Михайлович Достоевский ``` 我们显示创建文件的内容。 ## C# `XmlTextReader` 我们可以使用流来读取 XML 数据。 `XmlTextReader`是用于读取 C# 中的 XML 文件的类。 该类是仅转发和只读的。 我们有以下 XML 测试文件: `languages.xml` ```cs <?xml version="1.0" encoding="utf-8" ?> <languages> <language>Python</language> <language>Ruby</language> <language>Javascript</language> <language>C#</language> </languages> ``` 此文件包含自定义 XML 标记之间的语言名称。 `Program.cs` ```cs using System; using System.IO; using System.Xml; namespace ReadingXMLFile { public class Program { static void Main() { string path = @"C:\Users\Jano\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 文件读取数据并将其打印到终端。 ```cs using System.Xml; ``` `System.Xml`命名空间包含与 Xml 读写相关的类。 ```cs using (var xreader = new XmlTextReader(path)) ``` 创建一个`XmlTextReader`对象。 它是一种读取器,可提供对 XML 数据的快速,非缓存且仅前向访问。 它以文件名作为参数。 ```cs xreader.MoveToContent(); ``` `MoveToContent()`方法移至 XML 文件的实际内容。 ```cs while (xreader.Read()) ``` 该行从流中读取下一个节点。 如果没有更多节点,则`Read()`方法返回`false`。 ```cs var node = xreader.NodeType switch { XmlNodeType.Element => String.Format("{0}: ", xreader.Name), XmlNodeType.Text => String.Format("{0} \n", xreader.Value), _ => "" }; Console.Write(node); ``` 在这里,我们打印元素名称和元素文本。 ```cs $ dotnet run language: Python language: Ruby language: Javascript language: C# ``` 这是示例的输出。 ## C# 创建,移动目录 `System.IO.Directory`是一个类,具有用于在目录和子目录中创建,移动和枚举的静态方法。 `Program.cs` ```cs using System; using System.IO; namespace DirectoryEx { class Program { static void Main(string[] args) { Directory.CreateDirectory("temp"); Directory.CreateDirectory("newdir"); Directory.Move("temp", "temporary"); } } } ``` 我们创建两个目录,然后重命名其中一个目录。 目录在项目文件夹中创建。 ```cs Directory.CreateDirectory("temp"); ``` `CreateDirectory()`方法创建一个新目录。 ```cs Directory.Move("temp", "temporary"); ``` `Move()`方法为指定的目录提供一个新名称。 ## C# `DirectoryInfo` `DirectoryInfo`公开了用于在目录和子目录中创建,移动和枚举的实例方法。 `Program.cs` ```cs using System; using System.IO; namespace ShowContents { class Program { static void Main(string[] args) { var path = @"C:\Users\Jano\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`类遍历特定目录并打印其内容。 ```cs var path = @"C:\Users\Jano\Documents"; var DirInfo = new DirectoryInfo(path); ``` 我们显示指定目录的内容。 ```cs string[] files = Directory.GetFiles(path);; ``` 我们使用静态`GetFiles()`方法获取目录的所有文件。 ```cs DirectoryInfo[] dirs = dir.GetDirectories(); ``` 我们得到所有目录。 ```cs foreach (DirectoryInfo subDir in dirs) { Console.WriteLine(subDir.Name); } ``` 在这里,我们遍历目录并将其名称打印到控制台。 ```cs foreach (string fileName in files) { Console.WriteLine(fileName); } ``` 在这里,我们遍历文件数组并将其名称打印到控制台。 在本章中,我们介绍了 C# 中的输入/输出操作。