C# 文件的输入与输出(I/O)详解
全面掌握 C# 中的文件输入与输出操作,下面将从核心概念、常用类、同步 / 异步操作、文件内容读写、目录与文件管理等方面进行系统化讲解,涵盖基础到实战的全流程
一、C# 文件 I/O 的核心基础
1. 核心命名空间
C# 的文件 I/O 操作主要依赖 .NET Base Class Library (BCL) 中的两个核心命名空间,使用前需先引用:
csharp
// 核心:用于文件、目录的创建、删除、移动等管理操作,以及基础文件流
using System.IO;
// 进阶:用于更高效的字符编码处理、便捷的文本读写(推荐优先使用)
using System.IO.Compression; // 可选,用于压缩文件I/O
using System.Text; // 可选,用于指定字符编码(如UTF-8、GBK)
2. 核心操作类型
C# 文件 I/O 分为两大核心类型,满足不同场景需求:
- 字节流操作:以byte(字节)为单位读写数据,适用于所有文件类型(如二进制文件、图片、视频、音频等),核心类为FileStream。
- 字符流操作:以char(字符)为单位读写数据,专门用于文本文件(如.txt、.csv、.json等),自动处理字符编码,核心类为StreamReader、StreamWriter、File(静态辅助类)、FileInfo(实例化操作类)。
二、常用文件 I/O 类及其分工
C# 提供了多个分工明确的文件 I/O 类,避免单一类的功能臃肿,核心类的职责如下:
| 类名 | 类型 | 核心职责 |
|---|---|---|
| File | 静态类 | 提供静态方法,快速完成文件的创建、删除、移动、复制,以及文本 / 字节的快速读写(无需手动管理流的关闭)。 |
| FileInfo | 实例类 | 提供实例方法,功能与File类似,适合对同一个文件进行多次操作(性能更优,无需重复解析文件路径)。 |
| FileStream | 实例类 | 底层字节流类,支持同步 / 异步的字节级读写,是所有字符流类的底层依赖,适用于二进制文件操作。 |
| StreamReader | 实例类 | 基于FileStream封装,专门用于读取文本文件,自动处理字符编码,简化文本读取逻辑。 |
| StreamWriter | 实例类 | 提基于FileStream封装,专门用于写入文本文件,自动处理字符编码,简化文本写入逻辑。 |
| Directory | 静态类 | 提供静态方法,管理目录(文件夹)的创建、删除、移动、查询子目录 / 文件。 |
| DirectoryInfo | 实例类 | 提供实例方法,适合对同一个目录进行多次操作,性能更优。 |
三、文本文件的输入与输出(最常用)
文本文件操作是日常开发中最频繁的场景,推荐优先使用File静态类(快速操作)或StreamReader/StreamWriter(复杂场景,如大文件、指定编码)。
3.1 快速读写文本文件(使用File静态类)
File类提供了一系列便捷方法,无需手动打开 / 关闭文件流,简化代码编写,适合小到中等大小的文本文件。
(1)一次性读取整个文本文件
提供 3 个核心方法,满足不同读取需求:
csharp
using System;
using System.IO;
using System.Text;
public class TextFileQuickOperation
{
public static void ReadTextFile()
{
string filePath = "test.txt"; // 文件路径(相对路径或绝对路径)
// 方法1:读取整个文件为一个字符串(最常用)
if (File.Exists(filePath)) // 先判断文件是否存在,避免异常
{
string fullContent = File.ReadAllText(filePath, Encoding.UTF8);
Console.WriteLine("=== 一次性读取整个文件内容 ===");
Console.WriteLine(fullContent);
}
// 方法2:读取整个文件为字符串数组(每行对应一个数组元素)
if (File.Exists(filePath))
{
string[] allLines = File.ReadAllLines(filePath, Encoding.UTF8);
Console.WriteLine("\n=== 按行读取文件内容 ===");
for (int i = 0; i < allLines.Length; i++)
{
Console.WriteLine($"第{i+1}行:{allLines[i]}");
}
}
// 方法3:读取整个文件为字节数组(兼容文本/二进制文件)
if (File.Exists(filePath))
{
byte[] byteContent = File.ReadAllBytes(filePath);
// 转换为字符串(指定编码)
string content = Encoding.UTF8.GetString(byteContent);
Console.WriteLine("\n=== 以字节数组形式读取文件内容 ===");
Console.WriteLine(content);
}
}
}
(2)一次性写入整个文本文件
核心方法支持覆盖写入和追加写入,注意:WriteAll*系列方法会覆盖原有文件内容,若文件不存在则自动创建。
csharp
public static void WriteTextFile()
{
string filePath = "test.txt";
string content = "这是通过File类写入的文本内容\n第二行内容\n第三行内容";
string[] lines = new[] { "数组第一行", "数组第二行", "数组第三行" };
// 方法1:写入整个字符串到文件(覆盖原有内容)
File.WriteAllText(filePath, content, Encoding.UTF8);
Console.WriteLine("字符串内容已写入文件");
// 方法2:写入字符串数组到文件(每行对应一个数组元素,覆盖原有内容)
File.WriteAllLines(filePath, lines, Encoding.UTF8);
Console.WriteLine("字符串数组内容已写入文件");
// 方法3:追加内容到文件末尾(不覆盖原有内容,文件不存在则创建)
File.AppendAllText(filePath, "\n这是追加的内容", Encoding.UTF8);
File.AppendAllLines(filePath, new[] { "追加的行1", "追加的行2" }, Encoding.UTF8);
Console.WriteLine("内容已追加到文件末尾");
}
3.2 逐行 / 按需读写文本文件(使用StreamReader/StreamWriter)
(1)使用StreamReader读取文本文件(逐行 / 字符)
推荐使用using语句包裹流对象,自动释放文件资源(无需手动调用Close()或Dispose()),避免文件句柄泄露。
csharp
public static void ReadTextFileWithStreamReader()
{
string filePath = "large_test.txt";
// using 语句自动释放 StreamReader 资源,确保文件关闭
using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8))
{
Console.WriteLine("=== 逐行读取大文本文件 ===");
string line;
// 逐行读取,直到文件末尾(返回null表示读取完成)
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
// 可在此处添加业务逻辑(如筛选、解析数据)
}
// 可选:重置读取位置(从头开始再次读取)
// reader.BaseStream.Seek(0, SeekOrigin.Begin);
// reader.DiscardBufferedData();
// 可选:读取单个字符
// int ch;
// while ((ch = reader.Read()) != -1)
// {
// Console.Write((char)ch);
// }
}
}
(2)使用StreamWriter写入文本文件(逐行 / 字符)
支持覆盖写入和追加写入,通过构造函数参数控制写入模式。`
csharp
public static void WriteTextFileWithStreamWriter()
{
string filePath = "large_test.txt";
// 构造函数第二个参数:true=追加写入,false=覆盖写入(默认)
using (StreamWriter writer = new StreamWriter(filePath, true, Encoding.UTF8))
{
// 写入单行文本(自动换行)
writer.WriteLine("通过StreamWriter写入的第一行");
writer.WriteLine("通过StreamWriter写入的第二行");
// 写入文本(不自动换行)
writer.Write("这是不自动换行的内容");
writer.Write(" 紧跟在前面的内容之后");
// 写入格式化文本
writer.WriteLine("\n格式化文本:{0},{1}", "参数1", "参数2");
// 强制刷新缓冲区(将内存中的数据写入磁盘,默认关闭流时自动刷新)
writer.Flush();
}
Console.WriteLine("大文件内容写入/追加完成");
}
四、二进制文件的输入与输出(FileStream)
二进制文件(如图片、视频、音频、.dat、.exe等)无法通过文本类读写,需使用FileStream以字节为单位进行操作,核心是Read()和Write()方法。
4.1 二进制文件读取(FileStream.Read())
csharp
public static void ReadBinaryFile()
{
string sourceFilePath = "image.jpg"; // 二进制文件路径(图片示例)
string targetFilePath = "image_copy.jpg"; // 复制后的文件路径
// 读取二进制文件到字节数组
byte[] buffer;
using (FileStream fsRead = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
{
// 初始化字节数组,长度为文件大小
buffer = new byte[fsRead.Length];
// 读取文件内容到字节数组(返回实际读取的字节数)
int readBytes = fsRead.Read(buffer, 0, buffer.Length);
Console.WriteLine($"成功读取 {readBytes} 字节数据");
}
// (可选)将字节数组写入新文件(实现文件复制)
using (FileStream fsWrite = new FileStream(targetFilePath, FileMode.Create, FileAccess.Write))
{
fsWrite.Write(buffer, 0, buffer.Length);
Console.WriteLine("二进制文件复制完成");
}
}
4.2 二进制文件写入(FileStream.Write())
csharp
public static void WriteBinaryFile()
{
string filePath = "data.dat";
// 准备要写入的二进制数据(示例:整数、字符串转换为字节数组)
int intData = 12345;
string strData = "二进制文件中的字符串数据";
byte[] intBytes = BitConverter.GetBytes(intData);
byte[] strBytes = Encoding.UTF8.GetBytes(strData);
using (FileStream fsWrite = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
// 写入整数对应的字节数组
fsWrite.Write(intBytes, 0, intBytes.Length);
// 写入字符串对应的字节数组
fsWrite.Write(strBytes, 0, strBytes.Length);
Console.WriteLine("二进制数据写入完成");
}
}
4.3 关键参数说明
FileStream构造函数的核心参数(决定文件操作行为):
- FileMode:文件打开 / 创建模式(Open= 打开已存在文件、Create= 创建新文件(存在则覆盖)、CreateNew= 创建新文件(存在则抛异常)、Append= 追加模式打开文件等)。
- FileAccess:文件访问权限(Read= 只读、Write= 只写、ReadWrite= 读写)。
- FileShare:文件共享权限(可选,控制其他进程对该文件的访问方式)。
五、目录与文件的管理操作
除了文件内容读写,C# 还提供了Directory/DirectoryInfo、File/FileInfo用于目录和文件的管理(创建、删除、移动等)。
5.1 目录管理(Directory 静态类)
csharp
public static void DirectoryManagement()
{
string dirPath = "TestDirectory/SubDirectory";
// 1. 创建目录(支持多级目录创建,无需逐级创建)
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
Console.WriteLine("目录创建完成");
}
// 2. 获取目录信息(如创建时间、子目录、文件列表)
string[] subDirs = Directory.GetDirectories("TestDirectory");
string[] files = Directory.GetFiles("TestDirectory", "*.txt", SearchOption.AllDirectories);
Console.WriteLine($"目录下子目录数量:{subDirs.Length}");
Console.WriteLine($"目录下txt文件数量:{files.Length}");
// 3. 移动目录(剪切粘贴,支持跨盘符?部分系统不支持,推荐同盘符操作)
// Directory.Move(dirPath, "TestDirectory/NewSubDirectory");
// 4. 删除目录(true=删除目录及其所有子目录/文件,false=仅删除空目录)
// Directory.Delete(dirPath, true);
}
5.2 文件管理(File 静态类)
csharp
public static void FileManagement()
{
string sourceFile = "test.txt";
string targetFile = "TestDirectory/test_copy.txt";
// 1. 复制文件(true=覆盖目标文件,false=不覆盖,存在则抛异常)
if (File.Exists(sourceFile))
{
File.Copy(sourceFile, targetFile, true);
Console.WriteLine("文件复制完成");
}
// 2. 移动文件(剪切粘贴,可重命名文件)
// File.Move(targetFile, "TestDirectory/test_rename.txt");
// 3. 获取文件信息(大小、创建时间、最后修改时间)
FileInfo fileInfo = new FileInfo(sourceFile);
Console.WriteLine($"文件大小:{fileInfo.Length} 字节");
Console.WriteLine($"创建时间:{fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
// 4. 删除文件(可删除指定文件,若文件不存在则抛异常,先判断Exists)
// if (File.Exists(targetFile))
// {
// File.Delete(targetFile);
// Console.WriteLine("文件删除完成");
// }
}
六、异步文件 I/O 操作(推荐高并发场景)
为了不阻塞主线程(尤其是 UI 程序、Web API),.NET 提供了异步文件 I/O 方法(后缀为Async),配合async/await关键字使用,性能更优。
6.1 异步读取文本文件
csharp
public static async Task ReadTextFileAsync()
{
string filePath = "test.txt";
if (File.Exists(filePath))
{
// 异步读取整个文本文件
string content = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
Console.WriteLine("=== 异步读取文本文件完成 ===");
Console.WriteLine(content);
// 异步逐行读取(StreamReader 异步方法)
using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
// 处理逐行数据
}
}
}
}
6.2 异步写入文本文件
csharp
public static async Task WriteTextFileAsync()
{
string filePath = "test.txt";
string content = "这是异步写入的文本内容";
// 异步写入文本文件
await File.WriteAllTextAsync(filePath, content, Encoding.UTF8);
// 异步追加文本文件
await File.AppendAllTextAsync(filePath, "\n这是异步追加的内容", Encoding.UTF8);
Console.WriteLine("异步写入/追加文件完成");
}
6.3 异步操作二进制文件(FileStream 异步方法)
csharp
public static async Task ReadWriteBinaryFileAsync()
{
string filePath = "data.dat";
byte[] buffer = Encoding.UTF8.GetBytes("异步写入的二进制数据");
// 异步写入二进制文件
using (FileStream fsWrite = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
await fsWrite.WriteAsync(buffer, 0, buffer.Length);
}
// 异步读取二进制文件
using (FileStream fsRead = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
byte[] readBuffer = new byte[buffer.Length];
await fsRead.ReadAsync(readBuffer, 0, readBuffer.Length);
string content = Encoding.UTF8.GetString(readBuffer);
Console.WriteLine($"异步读取二进制文件内容:{content}");
}
}
七、注意事项与最佳实践
- 1、资源释放:始终使用using语句包裹流对象(FileStream、StreamReader、StreamWriter),自动释放文件句柄,避免文件被占用无法删除 / 修改。
- 2、路径处理:避免硬编码绝对路径,推荐使用Path类处理路径(拼接、获取扩展名、获取临时目录等),提高跨平台兼容性。
csharp
// 安全拼接路径(自动处理目录分隔符 \ /)
string safePath = Path.Combine("TestDirectory", "subdir", "test.txt");
// 获取文件扩展名
string extension = Path.GetExtension(safePath); // 返回 .txt
- 3、异常处理:文件 I/O 操作容易抛出异常(如文件不存在、权限不足、磁盘满),需使用try-catch捕获并处理相关异常(FileNotFoundException、UnauthorizedAccessException等)。
- 4、编码指定:读写文本文件时明确指定编码(如Encoding.UTF8),避免默认编码(不同系统默认编码不同)导致中文乱码。
- 5、大文件处理:大文件优先使用StreamReader/StreamWriter逐行读写(文本文件)或FileStream分块读写(二进制文件),避免一次性加载到内存导致内存溢出。
八、总结
- 1、C# 文件 I/O 核心分为文本文件(字符流)和二进制文件(字节流)操作,对应不同的类库。
- 2、小文件优先使用File静态类(便捷高效),大文件 / 复杂场景优先使用StreamReader/StreamWriter/FileStream(内存友好)。
- 3、异步 I/O(async/await+*Async方法)是高并发场景的最佳选择,避免阻塞主线程。
- 4、文件 I/O 操作需注意资源释放、路径安全、编码指定和异常处理,确保程序健壮性。
- 5、目录与文件管理可通过Directory/DirectoryInfo、File/FileInfo实现,满足文件的复制、移动、删除等需求。