20-文件 IO 操作
文件 IO(输入/输出)操作用于读写文件和目录。C# 在 System.IO 命名空间中提供了丰富的文件操作类。
一、文件操作的核心类总览
| 类 | 特点 | 适用场景 |
|---|---|---|
File | 静态方法 | 简单的文件读写(少量数据) |
FileInfo | 实例方法 | 需要多次获取文件属性 |
Directory | 静态方法 | 目录创建、删除、查找 |
DirectoryInfo | 实例方法 | 需要多次获取目录属性 |
StreamReader/Writer | 文本流 | 大文件、逐行处理 |
BinaryReader/Writer | 二进制流 | 图片、序列化数据 |
FileStream | 字节流 | 底层精细控制 |
Path | 路径处理 | 路径拼接、解析扩展名 |
二、文件和目录操作
目录操作
csharp
using System.IO;
// 静态方法(File/Directory)
string dirPath = @"C:\MyFolder";
// 创建目录
Directory.CreateDirectory(dirPath);
// 判断是否存在
bool exists = Directory.Exists(dirPath);
// 获取文件列表
string[] files = Directory.GetFiles(dirPath, "*.txt"); // 只查 .txt 文件
string[] allFiles = Directory.GetFiles(dirPath, "*", SearchOption.AllDirectories); // 递归
// 获取子目录
string[] subDirs = Directory.GetDirectories(dirPath);
// 删除目录(true 表示递归删除所有子目录和文件)
Directory.Delete(dirPath, true);
// 移动/重命名
Directory.Move(dirPath, @"C:\NewFolder");
// 实例方法(DirectoryInfo)
DirectoryInfo dirInfo = new DirectoryInfo(dirPath);
if (!dirInfo.Exists)
dirInfo.Create();
foreach (FileInfo file in dirInfo.GetFiles())
{
Console.WriteLine($"{file.Name} - {file.Length} 字节 - {file.LastWriteTime}");
}文件操作
csharp
string filePath = @"C:\test.txt";
// 创建(注意:Create 返回 FileStream,需要关闭)
File.Create(filePath).Close(); // 创建空文件
// 复制
File.Copy(filePath, @"C:\test_backup.txt", overwrite: true);
// 移动(也可用于重命名)
File.Move(filePath, @"C:\new\test.txt");
// 删除
File.Delete(filePath);
// 判断是否存在
bool exists = File.Exists(filePath);
// 获取属性(FileInfo)
FileInfo fi = new FileInfo(filePath);
Console.WriteLine($"名称:{fi.Name}"); // test.txt
Console.WriteLine $"完整路径:{fi.FullName}"); // C:\test.txt
Console.WriteLine($"大小:{fi.Length} 字节");
Console.WriteLine($"扩展名:{fi.Extension}"); // .txt
Console.WriteLine($"创建时间:{fi.CreationTime}");
Console.WriteLine($"最后修改:{fi.LastWriteTime}");
Console.WriteLine($"是否为只读:{fi.IsReadOnly}");
// 属性操作
fi.IsReadOnly = true; // 设置为只读
fi.Attributes |= FileAttributes.Hidden; // 添加隐藏属性三、文本文件读写
一次性读写(适合小文件)
csharp
string filePath = @"C:\test.txt";
string content = "第一行内容\n第二行内容";
// 写入(覆盖)
File.WriteAllText(filePath, content);
// 追加
File.AppendAllText(filePath, "\n追加的内容");
// 读取全部
string text = File.ReadAllText(filePath);
// 逐行读写
string[] lines = { "行1", "行2", "行3" };
File.WriteAllLines(filePath, lines); // 写入多行
string[] readLines = File.ReadAllLines(filePath); // 读取所有行
foreach (string line in readLines)
Console.WriteLine(line);流式读写(适合大文件)
csharp
// StreamWriter:写入
using (StreamWriter writer = new StreamWriter(@"C:\output.txt"))
{
writer.WriteLine("第一行");
writer.WriteLine("第二行");
writer.Write("不换行");
writer.Write("继续写在同一行");
}
// 追加模式
using (StreamWriter writer = new StreamWriter(@"C:\output.txt", append: true))
{
writer.WriteLine("追加的内容");
}
// StreamReader:读取
using (StreamReader reader = new StreamReader(@"C:\output.txt"))
{
// 方式一:全部读取
string content = reader.ReadToEnd();
// 方式二:逐行读取(大文件推荐)
string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine($"行:{line}");
}
}
// 指定编码
using (StreamReader reader = new StreamReader(@"C:\file.txt", Encoding.UTF8))
using (StreamWriter writer = new StreamWriter(@"C:\file.txt", false, Encoding.UTF8))
{
// 处理文件...
}为什么要使用 using?
文件流使用操作系统资源(文件句柄),必须及时释放,否则其他进程无法访问该文件。
csharp
// ❌ 错误:未释放资源
var reader = new StreamReader(@"C:\test.txt");
string content = reader.ReadToEnd();
// 文件句柄未释放!
// ✅ 正确:using 语句(推荐)
using (var reader = new StreamReader(@"C:\test.txt"))
{
string text = reader.ReadToEnd();
} // 自动释放
// ✅ 正确:using 声明(C# 8.0+)
using var reader2 = new StreamReader(@"C:\test.txt");
string text2 = reader2.ReadToEnd();
// 作用域结束时自动释放File.ReadAllText vs StreamReader 选择
csharp
// 小文件(< 10MB)→ 简单方便
string content = File.ReadAllText(@"C:\small.txt");
// 大文件(> 10MB)→ 流式处理,避免内存爆满
using var reader = new StreamReader(@"C:\large.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
// 逐行处理,不需要将全部内容加载到内存
ProcessLine(line);
}四、二进制文件读写
BinaryWriter / BinaryReader
csharp
// 写入二进制数据
string filePath = @"C:\data.bin";
using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Create)))
{
writer.Write(12345); // int
writer.Write(3.14159); // double
writer.Write("Hello World"); // string
writer.Write(true); // bool
writer.Write(DateTime.Now.Ticks); // long
}
// 读取(必须按写入的顺序和类型读取)
using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open)))
{
int intVal = reader.ReadInt32();
double dblVal = reader.ReadDouble();
string strVal = reader.ReadString();
bool boolVal = reader.ReadBoolean();
long longVal = reader.ReadInt64();
Console.WriteLine($"{intVal}, {dblVal}, {strVal}, {boolVal}");
}文件复制示例
csharp
static void CopyFile(string sourcePath, string destPath)
{
const int bufferSize = 8192; // 缓冲区大小
using (FileStream source = File.OpenRead(sourcePath))
using (FileStream dest = File.OpenWrite(destPath))
{
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = source.Read(buffer, 0, bufferSize)) > 0)
{
dest.Write(buffer, 0, bytesRead);
}
}
Console.WriteLine($"文件已复制:{sourcePath} → {destPath}");
}
// 测试
CopyFile(@"C:\large.zip", @"D:\backup\large.zip");五、FileStream(底层字节流)
FileStream 提供最底层的文件读写控制,适用于图片、视频等二进制文件。
csharp
string path = @"C:\stream.dat";
// 写入
byte[] data = Encoding.UTF8.GetBytes("Hello FileStream");
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
fs.Write(data, 0, data.Length);
Console.WriteLine($"写入 {data.Length} 字节");
}
// 读取
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[fs.Length];
int bytesRead = fs.Read(buffer, 0, buffer.Length);
string result = Encoding.UTF8.GetString(buffer);
Console.WriteLine($"读取 {bytesRead} 字节:{result}");
}FileMode 枚举
| 值 | 说明 | 文件不存在时 | 文件存在时 |
|---|---|---|---|
CreateNew | 创建新文件 | 创建新文件 | 抛出异常 |
Create | 创建(覆盖) | 创建新文件 | 覆盖 |
Open | 打开已有文件 | 抛出异常 | 打开 |
OpenOrCreate | 打开或创建 | 创建新文件 | 打开 |
Append | 追加 | 创建新文件 | 定位到末尾 |
FileAccess 枚举
| 值 | 说明 |
|---|---|
Read | 只读 |
Write | 只写 |
ReadWrite | 读写 |
FileShare 枚举
csharp
// 控制其他进程/线程同时访问的权限
using (FileStream fs = new FileStream(
@"C:\test.txt",
FileMode.Open,
FileAccess.Read,
FileShare.Read)) // 允许其他进程读取(但不能写入)
{
// 读取文件...
}| 值 | 说明 |
|---|---|
None | 拒绝共享(独占) |
Read | 允许其他进程读取 |
Write | 允许其他进程写入 |
ReadWrite | 允许其他进程读写 |
Delete | 允许其他进程删除 |
六、路径操作(Path 类)
Path 类提供跨平台的路径处理方法,比字符串拼接更安全。
csharp
string path = @"C:\Users\张三\Documents\test.txt";
Console.WriteLine(Path.GetFileName(path)); // test.txt
Console.WriteLine(Path.GetFileNameWithoutExtension(path)); // test
Console.WriteLine(Path.GetExtension(path)); // .txt
Console.WriteLine(Path.GetDirectoryName(path)); // C:\Users\张三\Documents
Console.WriteLine(Path.GetFullPath(path)); // 完整路径(解析相对路径)
// 路径组合(自动处理分隔符)
string fullPath = Path.Combine(@"C:\Users", "张三", "Documents", "test.txt");
// 更改扩展名
string newPath = Path.ChangeExtension(path, ".log"); // C:\Users\张三\Documents\test.log
// 临时文件
string tempFile = Path.GetTempFileName(); // 创建临时文件并返回路径
string tempDir = Path.GetTempPath(); // 临时文件夹路径
// 文件名合法性检查
char[] invalidChars = Path.GetInvalidFileNameChars();
bool isValid = path.IndexOfAny(invalidChars) == -1;
// 获取当前程序运行目录
string currentDir = Environment.CurrentDirectory;
string appDir = AppDomain.CurrentDomain.BaseDirectory;七、异步文件操作
对大文件使用异步方法避免阻塞主线程。
csharp
// 异步读取
static async Task<string> ReadFileAsync(string path)
{
using StreamReader reader = new StreamReader(path);
return await reader.ReadToEndAsync();
}
// 异步写入
static async Task WriteFileAsync(string path, string content)
{
using StreamWriter writer = new StreamWriter(path);
await writer.WriteAsync(content);
}
// 异步复制
static async Task CopyFileAsync(string source, string dest)
{
using FileStream sourceStream = File.OpenRead(source);
using FileStream destStream = File.OpenWrite(dest);
await sourceStream.CopyToAsync(destStream);
}
// 使用
string content = await ReadFileAsync(@"C:\large.txt");
await WriteFileAsync(@"C:\output.txt", "异步写入内容");
await CopyFileAsync(@"C:\source.zip", @"C:\dest.zip");八、综合案例
案例一:日志记录器
csharp
public class FileLogger : IDisposable
{
private readonly string logPath;
private readonly StreamWriter writer;
private readonly object lockObj = new object();
public FileLogger(string logDirectory = "Logs")
{
Directory.CreateDirectory(logDirectory);
logPath = Path.Combine(logDirectory, $"{DateTime.Now:yyyy-MM-dd}.log");
writer = new StreamWriter(logPath, append: true);
writer.AutoFlush = true; // 自动刷新缓冲区
}
public void Log(string level, string message)
{
string line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{level}] {message}";
lock (lockObj) // 线程安全
{
writer.WriteLine(line);
}
}
public void Info(string msg) => Log("INFO", msg);
public void Warn(string msg) => Log("WARN", msg);
public void Error(string msg) => Log("ERROR", msg);
public void Dispose()
{
writer?.Dispose();
}
}
// 使用
using (var logger = new FileLogger())
{
logger.Info("系统启动");
logger.Warn("内存使用率超过 80%");
logger.Error("数据库连接失败");
}案例二:文件批量重命名工具
csharp
static void BatchRename(string directory, string searchPattern, string newPrefix)
{
string[] files = Directory.GetFiles(directory, searchPattern);
int count = 0;
foreach (string filePath in files)
{
FileInfo fi = new FileInfo(filePath);
// 新文件名:前缀 + 序号 + 原扩展名
string newName = $"{newPrefix}_{++count:D3}{fi.Extension}";
string newPath = Path.Combine(directory, newName);
File.Move(filePath, newPath);
Console.WriteLine($"重命名:{fi.Name} → {newName}");
}
Console.WriteLine($"\n共重命名 {count} 个文件");
}
// 使用:将 C:\Photos 下所有 .jpg 文件重命名为 Photo_001.jpg, Photo_002.jpg...
BatchRename(@"C:\Photos", "*.jpg", "Photo");案例三:INI 配置文件读写
csharp
public class IniFile
{
private readonly string filePath;
private Dictionary<string, Dictionary<string, string>> data = new();
public IniFile(string filePath)
{
this.filePath = filePath;
Load();
}
private void Load()
{
if (!File.Exists(filePath)) return;
string currentSection = "";
foreach (string line in File.ReadAllLines(filePath))
{
string trimmed = line.Trim();
if (trimmed.StartsWith(";") || trimmed.StartsWith("#")) continue; // 注释
if (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
{
currentSection = trimmed[1..^1];
data[currentSection] = new Dictionary<string, string>();
}
else if (trimmed.Contains('='))
{
int idx = trimmed.IndexOf('=');
string key = trimmed[..idx].Trim();
string value = trimmed[(idx + 1)..].Trim();
if (!data.ContainsKey(currentSection))
data[currentSection] = new Dictionary<string, string>();
data[currentSection][key] = value;
}
}
}
public string Get(string section, string key, string defaultValue = "")
{
if (data.TryGetValue(section, out var sectionData))
if (sectionData.TryGetValue(key, out string value))
return value;
return defaultValue;
}
public void Set(string section, string key, string value)
{
if (!data.ContainsKey(section))
data[section] = new Dictionary<string, string>();
data[section][key] = value;
Save();
}
private void Save()
{
using var writer = new StreamWriter(filePath);
foreach (var section in data)
{
writer.WriteLine($"[{section.Key}]");
foreach (var kvp in section.Value)
writer.WriteLine($"{kvp.Key}={kvp.Value}");
writer.WriteLine();
}
}
}
// 使用
var config = new IniFile(@"C:\config.ini");
string server = config.Get("Database", "Server", "localhost");
int port = int.Parse(config.Get("Database", "Port", "1433"));
config.Set("User", "Name", "张三");核心知识点总结
文件读写方式选择
| 场景 | 推荐方式 | 示例 |
|---|---|---|
| 小文本文件(一次性) | File.ReadAllText/WriteAllText | 配置文件、日志记录 |
| 大文本文件(逐行) | StreamReader/Writer | 日志文件分析、CSV 处理 |
| 二进制文件 | BinaryReader/Writer | 图片、序列化 |
| 底层字节控制 | FileStream | 文件复制、自定义格式 |
| 异步操作 | ReadAsync/WriteAsync | 大文件、UI 不阻塞 |
注意事项
- 总是使用
using——确保文件资源及时释放 - 检查
File.Exists——打开不存在的文件会抛出FileNotFoundException - 路径使用
Path.Combine——避免手动拼接路径字符串 - 编码问题——默认 UTF-8,读取非 UTF-8 文件时指定编码
- 权限问题——写入系统目录(如
C:\Windows)可能需要管理员权限 - 异常处理——文件操作可能抛出
UnauthorizedAccessException、IOException、DirectoryNotFoundException等 - 跨平台路径——
Path类在不同操作系统上使用正确的路径分隔符(/或\)


