C# 文件的输入与输出(I/O)详解

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实现,满足文件的复制、移动、删除等需求。
相关推荐
千层冷面2 小时前
数据库分库分表
java·数据库·mysql·oracle
CodeByV2 小时前
【算法题】堆
算法
kaikaile19952 小时前
A星算法避开障碍物寻找最优路径(MATLAB实现)
数据结构·算法·matlab
今天_也很困2 小时前
LeetCode 热题100-15.三数之和
数据结构·算法·leetcode
努力的小陈^O^3 小时前
问题:Spring循环依赖问题排查与解决
java·开发语言·前端
企业对冲系统官3 小时前
基差风险管理系统日志分析功能的架构与实现
大数据·网络·数据库·算法·github·动态规划
kylezhao20193 小时前
C# TreeView 控件详解与应用
c#
ldccorpora3 小时前
GALE Phase 1 Chinese Broadcast News Parallel Text - Part 1数据集介绍,官网编号LDC2007T23
人工智能·深度学习·算法·机器学习·自然语言处理
千金裘换酒3 小时前
LeetCode 数组经典题刷题
算法·leetcode·职场和发展