ZetCode

C# 输入 & 输出

最后修改于 2023 年 7 月 5 日

在本文中,我们将介绍 C# 中的输入 & 输出操作。C# 中的输入 & 输出基于流 (streams)。

C# 流

是字节序列的抽象,例如文件、输入/输出设备、进程间通信管道或 TCP/IP 套接字。流将数据从一个点传输到另一个点。流也能够操作数据;例如,它们可以压缩或加密数据。在 .NET 中,System.IO 命名空间包含允许在数据流和文件上进行读取和写入的类型。

C# 在 File 类中提供了更高级别的 I/O 操作方法,并在 StreamReaderStreamWriter 等类中提供了更低级别的方法。

处理异常

I/O 操作容易出错。我们可能会遇到诸如 FileNotFoundExceptionUnauthorizedAccessException 之类的异常。与 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.

C# File.ReadAllText

File 提供了用于创建、复制、删除、移动和打开单个文件的静态方法,并有助于创建 FileStream 对象。

注意: File.ReadAllText 不适合读取非常大的文件。

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.

C# File.ReadAllLines

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 循环中遍历数组并将每一行打印到控制台。

C# 创建文件

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");
...

我们将两行写入流。

C# 创建、上次写入、上次访问时间

使用 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;否则,返回 false。如果 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

C# 复制文件

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 方法复制文件。第三个参数指定如果文件存在,是否应该覆盖它。

C# IDisposable 接口

流实现 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 方法清理资源。使用 null-conditinal 运算符,我们仅在变量不为 null 时才调用该方法。

C# using 语句

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 处理。

C# using 声明

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 方法的末尾),资源会自动清理。

C# MemoryStream

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);

该行创建一个容量为六个字节的 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
7

C# StreamReader

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

C# 统计行数

在下一个示例中,我们将统计行数。

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");

我们使用 StreamReader 和 while 循环。

while(stream.ReadLine() != null)
{
    count++;
}

在 while 循环中,我们使用 ReadLine 方法从流中读取一行。如果到达输入流的末尾,它将返回流中的一行或 null

$ dotnet run
There are 3 lines

C# StreamWriter

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 接受一个路径作为参数。如果文件存在,则将其覆盖;否则,将创建一个新文件。

C# FileStream

FileStream 为文件提供流,支持同步和异步读取和写入操作。

StreamReaderStreamWriter 处理文本数据,而 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 System.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
Фёдор Михайлович Достоевский

我们显示已创建文件的内容。

C# XmlTextReader

我们可以使用流来读取 XML 数据。XmlTextReader 是在 C# 中读取 XML 文件的类。该类是仅向前和只读的。

我们有以下 XML 测试文件

languages.xml
<?xml version="1.0" encoding="utf-8" ?>
<languages>
    <language>Python</language>
    <language>Ruby</language>
    <language>Javascript</language>
    <language>C#</language>
</languages>

此文件包含自定义 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#

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 方法为指定的目录提供一个新名称。

C# DirectoryInfo

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);
}

在这里,我们循环遍历文件数组并将它们的名称打印到控制台。

来源

文件和流 I/O

在本文中,我们介绍了 C# 中的输入/输出操作。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 C# 教程