本章专门介绍 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
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;
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
文件的内容,并将其打印到控制台。
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
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
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
文件,并将一些汽车名称写入其中。
using (StreamWriter sw = File.CreateText(path))
CreateText()
方法创建或打开一个文件,用于写入 UTF-8 编码的文本。 它返回一个StreamWriter
对象。
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
...
我们向流写入两行。
$ cat C:\Users\Jano\Documents\cars.txt
Hummer
Skoda
BMW
Volkswagen
Volvo
我们已成功将五个汽车名称写入文件。
C# 创建,最后写入,最后访问时间
使用File
类,我们可以获取文件的创建,最后写入和最后访问时间。 Exists()
方法确定指定的文件是否存在。
Program.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));
}
}
}
}
如果存在指定的文件,我们将确定其创建,最后写入和最后访问时间。
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
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
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");
}
}
}
然后,我们将文件的内容复制到另一个文件。
var srcPath = @"C:\Users\Jano\Documents\cars.txt";
var destPath = @"C:\Users\Jano\Documents\cars2.txt";
这是源文件和目标文件。
File.Copy(srcPath, destPath, true);
Copy()
方法复制文件。 第三个参数指定是否应覆盖文件(如果存在)。
$ dotnet run
File copied
This is a sample output.
C# IDisposable
接口
流实现IDisposable
接口。 实现此接口的对象必须尽早手动处理。 这是通过在finally
块中调用Dispose()
方法或利用using
语句来完成的。
Program.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();
}
}
}
}
在此示例中,我们从磁盘上的文件读取字符。 我们手动释放分配的资源。
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
时,才调用该方法。
C# 使用语句
using
语句定义一个范围,在该范围的末尾将放置一个对象。 它提供了一种方便的语法,可确保正确使用IDisposable
对象。
Program.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
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
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
将六个数字写入存储器。 然后,我们读取这些数字并将其打印到控制台。
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
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()
方法逐行读取文件。
while (sr.Peek() >= 0)
{
Console.WriteLine(sr.ReadLine());
}
Peek()
方法返回下一个可用字符,但不使用它。 它指示我们是否可以再次调用ReadLine()
方法。 如果没有要读取的字符,则返回-1
。
C# 计数行
在下一个示例中,我们将对行进行计数。
Program.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);
}
}
}
我们正在计算文件中的行数。
while(stream.ReadLine() != null)
{
count++;
}
在while
循环中,我们使用ReadLine()
方法从流中读取一行。 如果到达输入流的末尾,它将从流或null
中返回一行。
$ dotnet run
There are 3 lines
该文件有三行。
C# StreamWriter
StreamWriter
以特定编码将字符写入流。
Program.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
将字符串写入文件。
using (var sw = new StreamWriter(path))
我们创建一个新的StreamWriter
。 默认值是 UTF-8。 StreamWriter
将路径作为参数。 如果文件存在,它将被覆盖; 否则,将创建一个新文件。
$ dotnet run
$ cat C:\Users\Jano\Documents\newfile.txt
Today is a beautiful day.
我们已经使用cat
命令显示了文件的内容。
C# FileStream
FileStream
为文件提供流,同时支持同步和异步读取和写入操作。
StreamReader
和StreamWriter
处理文本数据,而FileStream
处理字节。
Program.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);
}
}
}
我们用俄语西里尔字母写一些文本到文件中。
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 C:\Users\Jano\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;
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 文件读取数据并将其打印到终端。
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
using System;
using System.IO;
namespace DirectoryEx
{
class Program
{
static void Main(string[] args)
{
Directory.CreateDirectory("temp");
Directory.CreateDirectory("newdir");
Directory.Move("temp", "temporary");
}
}
}
我们创建两个目录,然后重命名其中一个目录。 目录在项目文件夹中创建。
Directory.CreateDirectory("temp");
CreateDirectory()
方法创建一个新目录。
Directory.Move("temp", "temporary");
Move()
方法为指定的目录提供一个新名称。
C# DirectoryInfo
DirectoryInfo
公开了用于在目录和子目录中创建,移动和枚举的实例方法。
Program.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
类遍历特定目录并打印其内容。
var path = @"C:\Users\Jano\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# 中的输入/输出操作。