第十二章我们学习了异常处理和调试,知道了如何让程序更健壮。但程序运行时的数据存储在内存中,一旦程序关闭就会丢失。这一章我们要学习文件 I/O(Input/Output),让程序能够把数据保存到硬盘上,实现数据的持久化存储。
13.1 文件系统基础
13.1.1 为什么需要文件操作?
csharp
// 没有文件操作:数据只在内存中
string[] students = { "张三", "李四", "王五" };
// 程序关闭后,数据就丢失了
// 有文件操作:数据可以持久化
File.WriteAllLines("students.txt", students); // 保存到文件
string[] loaded = File.ReadAllLines("students.txt"); // 从文件读取
13.1.2 .NET 中文件操作的主要命名空间
| 命名空间 | 主要用途 |
|---|---|
System.IO |
文件和流操作的核心类 |
System.Text |
编码处理(UTF-8、ASCII 等) |
System.IO.Compression |
压缩和解压缩 |
13.1.3 关键类概览
text
System.IO
├── File // 静态方法操作文件(创建、复制、删除、移动)
├── FileInfo // 实例方法操作文件,提供更多属性
├── Directory // 静态方法操作目录
├── DirectoryInfo // 实例方法操作目录
├── Path // 路径字符串处理
├── FileStream // 文件字节流(底层)
├── StreamReader // 文本文件读取(高层)
├── StreamWriter // 文本文件写入(高层)
├── BinaryReader // 二进制文件读取
└── BinaryWriter // 二进制文件写入
13.2 路径操作(Path 类)
13.2.1 Path 类的常用方法
csharp
using System.IO;
string path = @"C:\MyFolder\subfolder\data.txt";
// 获取文件名(带扩展名)
string fileName = Path.GetFileName(path); // "data.txt"
// 获取文件名(不带扩展名)
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(path); // "data"
// 获取扩展名
string extension = Path.GetExtension(path); // ".txt"
// 获取目录路径
string directory = Path.GetDirectoryName(path); // "C:\MyFolder\subfolder"
// 获取根目录
string root = Path.GetPathRoot(path); // "C:\"
// 组合路径(推荐使用)
string combined = Path.Combine(@"C:\MyFolder", "subfolder", "data.txt");
// 结果:C:\MyFolder\subfolder\data.txt
// 获取临时文件路径
string tempFile = Path.GetTempFileName(); // 创建临时文件并返回路径
string tempPath = Path.GetTempPath(); // 获取临时目录
// 修改扩展名
string changed = Path.ChangeExtension(path, ".bak"); // "data.bak"
// 检查路径是否包含非法字符
bool hasInvalidChars = Path.GetInvalidPathChars().Any(c => path.Contains(c));
// 获取随机文件名
string randomName = Path.GetRandomFileName(); // "s4f3k2d4f3.dat"
// 创建有效路径(替换非法字符)
string invalidPath = "file:name?.txt";
char[] invalid = Path.GetInvalidFileNameChars();
string valid = string.Join("_", invalidPath.Split(invalid));
13.2.2 路径的最佳实践
csharp
class PathDemo
{
static void Main()
{
// ❌ 不好的做法:硬编码路径分隔符
string badPath = "C:\\MyFolder\\subfolder\\data.txt"; // Windows only
// ✅ 好的做法:使用 Path.Combine
string folder = @"C:\MyFolder";
string goodPath = Path.Combine(folder, "subfolder", "data.txt");
// ✅ 更好的做法:使用 AppDomain 获取应用程序目录
string appPath = AppDomain.CurrentDomain.BaseDirectory;
string configPath = Path.Combine(appPath, "config", "settings.json");
// ✅ 跨平台兼容
string crossPath = Path.Combine("folder", "subfolder", "file.txt");
// Windows: folder\subfolder\file.txt
// Linux/Mac: folder/subfolder/file.txt
Console.WriteLine($"应用目录:{appPath}");
Console.WriteLine($"配置文件路径:{configPath}");
}
}
13.3 File 和 FileInfo 类
13.3.1 File 类(静态方法)
csharp
using System.IO;
using System.Text;
class FileClassDemo
{
static void Main()
{
string filePath = "test.txt";
// 1. 写入文件
// 写入字符串(覆盖)
File.WriteAllText(filePath, "Hello, World!");
// 写入字符串(指定编码)
File.WriteAllText(filePath, "你好,世界!", Encoding.UTF8);
// 追加内容
File.AppendAllText(filePath, "\n追加一行");
// 写入行数组(覆盖)
string[] lines = { "第一行", "第二行", "第三行" };
File.WriteAllLines(filePath, lines);
// 追加行数组
File.AppendAllLines(filePath, new[] { "第四行", "第五行" });
// 写入字节
byte[] bytes = Encoding.UTF8.GetBytes("字节数据");
File.WriteAllBytes(filePath, bytes);
// 2. 读取文件
// 读取全部文本
string content = File.ReadAllText(filePath);
Console.WriteLine(content);
// 按行读取
string[] allLines = File.ReadAllLines(filePath);
foreach (string line in allLines)
{
Console.WriteLine(line);
}
// 逐行读取(适合大文件)
foreach (string line in File.ReadLines(filePath))
{
Console.WriteLine(line);
}
// 读取字节
byte[] fileBytes = File.ReadAllBytes(filePath);
// 3. 文件信息
bool exists = File.Exists(filePath);
DateTime created = File.GetCreationTime(filePath);
DateTime modified = File.GetLastWriteTime(filePath);
long length = new FileInfo(filePath).Length;
Console.WriteLine($"文件存在:{exists}");
Console.WriteLine($"创建时间:{created}");
Console.WriteLine($"修改时间:{modified}");
Console.WriteLine($"文件大小:{length} 字节");
// 4. 文件操作
// 复制
File.Copy(filePath, "copy.txt", overwrite: true);
// 移动/重命名
File.Move(filePath, "renamed.txt");
// 删除
File.Delete("copy.txt");
// 设置属性
File.SetAttributes("renamed.txt", FileAttributes.ReadOnly);
// 获取属性
FileAttributes attrs = File.GetAttributes("renamed.txt");
bool isReadOnly = (attrs & FileAttributes.ReadOnly) != 0;
// 取消只读
File.SetAttributes("renamed.txt", FileAttributes.Normal);
// 5. 编码处理
// 自动检测编码
var encoding = DetectEncoding(filePath);
Console.WriteLine($"文件编码:{encoding}");
}
static Encoding DetectEncoding(string filePath)
{
byte[] bytes = File.ReadAllBytes(filePath);
// UTF-8 BOM: EF BB BF
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
return Encoding.UTF8;
// UTF-32 BE BOM: 00 00 FE FF
if (bytes.Length >= 4 && bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0xFE && bytes[3] == 0xFF)
return Encoding.UTF32;
// UTF-32 LE BOM: FF FE 00 00
if (bytes.Length >= 4 && bytes[0] == 0xFF && bytes[1] == 0xFE && bytes[2] == 0x00 && bytes[3] == 0x00)
return Encoding.UTF32;
// UTF-16 BE BOM: FE FF
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
return Encoding.BigEndianUnicode;
// UTF-16 LE BOM: FF FE
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
return Encoding.Unicode;
return Encoding.Default; // 默认 ANSI
}
}
13.3.2 FileInfo 类(实例方法)
csharp
class FileInfoDemo
{
static void Main()
{
FileInfo file = new FileInfo("data.txt");
// 属性
Console.WriteLine($"文件名:{file.Name}");
Console.WriteLine($"完整路径:{file.FullName}");
Console.WriteLine($"目录:{file.DirectoryName}");
Console.WriteLine($"扩展名:{file.Extension}");
Console.WriteLine($"大小:{file.Length} 字节");
Console.WriteLine($"是否只读:{file.IsReadOnly}");
Console.WriteLine($"存在:{file.Exists}");
// 方法
if (!file.Exists)
{
// 创建并写入
using (StreamWriter sw = file.CreateText())
{
sw.WriteLine("Hello");
}
}
// 复制
FileInfo copy = file.CopyTo("backup.txt", overwrite: true);
// 移动/重命名
file.MoveTo("renamed.txt");
// 删除
file.Delete();
// 刷新(立即写入)
file.Refresh();
// 打开文件的多种方式
// 打开文本文件读取
using (StreamReader reader = file.OpenText())
{
string content = reader.ReadToEnd();
}
// 打开文本文件写入
using (StreamWriter writer = file.CreateText())
{
writer.WriteLine("新内容");
}
// 打开文件流(更底层控制)
using (FileStream fs = file.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
// 操作字节
}
}
}
13.4 目录操作(Directory 和 DirectoryInfo)
13.4.1 Directory 类(静态方法)
csharp
class DirectoryDemo
{
static void Main()
{
string dirPath = @"C:\MyTestFolder";
// 1. 创建目录
Directory.CreateDirectory(dirPath);
Directory.CreateDirectory(Path.Combine(dirPath, "subfolder1"));
Directory.CreateDirectory(Path.Combine(dirPath, "subfolder2"));
// 2. 检查是否存在
bool exists = Directory.Exists(dirPath);
Console.WriteLine($"目录存在:{exists}");
// 3. 获取目录信息
string currentDir = Directory.GetCurrentDirectory();
string[] logicalDrives = Directory.GetLogicalDrives(); // C:\, D:\...
Console.WriteLine($"当前目录:{currentDir}");
Console.WriteLine($"逻辑驱动器:{string.Join(", ", logicalDrives)}");
// 4. 枚举目录
// 获取子目录(不递归)
string[] subDirs = Directory.GetDirectories(dirPath);
// 获取子目录(带搜索模式)
string[] matchDirs = Directory.GetDirectories(dirPath, "sub*");
// 递归获取所有子目录
string[] allDirs = Directory.GetDirectories(dirPath, "*", SearchOption.AllDirectories);
// 5. 枚举文件
// 获取文件(不递归)
string[] files = Directory.GetFiles(dirPath);
// 获取指定类型文件
string[] txtFiles = Directory.GetFiles(dirPath, "*.txt");
// 递归获取所有文件
string[] allFiles = Directory.GetFiles(dirPath, "*", SearchOption.AllDirectories);
// 6. 枚举目录和文件
foreach (string entry in Directory.GetFileSystemEntries(dirPath))
{
if (Directory.Exists(entry))
Console.WriteLine($"📁 {entry}");
else
Console.WriteLine($"📄 {entry}");
}
// 7. 移动目录
Directory.Move(dirPath, @"C:\MovedFolder");
// 8. 删除目录
// 删除空目录
Directory.Delete(@"C:\emptyFolder");
// 删除非空目录(递归)
Directory.Delete(@"C:\MovedFolder", recursive: true);
// 9. 获取目录信息
DateTime created = Directory.GetCreationTime(dirPath);
DateTime modified = Directory.GetLastWriteTime(dirPath);
// 10. 设置当前目录
Directory.SetCurrentDirectory(dirPath);
// 11. 父目录
DirectoryInfo parent = Directory.GetParent(dirPath);
Console.WriteLine($"父目录:{parent?.FullName}");
}
}
13.4.2 DirectoryInfo 类(实例方法)
csharp
class DirectoryInfoDemo
{
static void Main()
{
DirectoryInfo dir = new DirectoryInfo(@"C:\MyFolder");
// 属性
Console.WriteLine($"名称:{dir.Name}");
Console.WriteLine($"完整路径:{dir.FullName}");
Console.WriteLine($"父目录:{dir.Parent?.Name}");
Console.WriteLine($"根目录:{dir.Root.Name}");
Console.WriteLine($"存在:{dir.Exists}");
// 创建
if (!dir.Exists)
{
dir.Create();
dir.CreateSubdirectory("subfolder");
}
// 枚举子目录(返回 DirectoryInfo 对象)
foreach (DirectoryInfo subDir in dir.GetDirectories())
{
Console.WriteLine($"📁 {subDir.Name} - {subDir.CreationTime}");
}
// 枚举文件(返回 FileInfo 对象)
foreach (FileInfo file in dir.GetFiles())
{
Console.WriteLine($"📄 {file.Name} - {file.Length} 字节");
}
// 带搜索模式
foreach (FileInfo file in dir.GetFiles("*.txt", SearchOption.AllDirectories))
{
Console.WriteLine(file.FullName);
}
// 移动
dir.MoveTo(@"C:\NewLocation");
// 删除
dir.Delete(recursive: true);
// 刷新状态
dir.Refresh();
}
}
13.4.3 递归遍历目录树
csharp
class DirectoryTreeWalker
{
public static void WalkDirectory(string path, string indent = "", int maxDepth = -1, int currentDepth = 0)
{
if (maxDepth >= 0 && currentDepth > maxDepth)
return;
try
{
DirectoryInfo dir = new DirectoryInfo(path);
// 输出当前目录
Console.WriteLine($"{indent}📁 {dir.Name}");
string newIndent = indent + " ";
// 输出文件(先输出文件)
try
{
foreach (FileInfo file in dir.GetFiles())
{
string size = FormatFileSize(file.Length);
Console.WriteLine($"{newIndent}📄 {file.Name} ({size})");
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{newIndent}[无权限访问文件]");
}
// 递归遍历子目录
try
{
foreach (DirectoryInfo subDir in dir.GetDirectories())
{
WalkDirectory(subDir.FullName, newIndent, maxDepth, currentDepth + 1);
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{newIndent}[无权限访问子目录]");
}
}
catch (DirectoryNotFoundException)
{
Console.WriteLine($"{indent}[目录不存在: {path}]");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{indent}[无权限访问: {path}]");
}
catch (Exception ex)
{
Console.WriteLine($"{indent}[错误: {ex.Message}]");
}
}
static string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
static void Main()
{
string startPath = @"C:\Program Files";
Console.WriteLine($"遍历目录:{startPath}\n");
WalkDirectory(startPath, maxDepth: 2); // 只遍历2层
}
}
13.5 流(Stream)基础
13.5.1 什么是流?
流 = 数据在源和目的地之间的字节序列
text
数据源(文件、网络、内存) → 流 → 目标(文件、网络、内存)
text
流类型层次:
Stream(抽象基类)
├── FileStream // 文件流
├── MemoryStream // 内存流
├── NetworkStream // 网络流
├── GZipStream // 压缩流
├── CryptoStream // 加密流
└── BufferedStream // 缓冲流
13.5.2 FileStream------文件字节流
csharp
class FileStreamDemo
{
static void Main()
{
string filePath = "data.bin";
// 1. 写入字节
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // "Hello" 的 ASCII
fs.Write(data, 0, data.Length);
Console.WriteLine($"写入 {data.Length} 字节");
}
// 2. 读取字节
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024];
int bytesRead = fs.Read(buffer, 0, buffer.Length);
string text = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine($"读取内容:{text}");
}
// 3. 使用 FileMode 枚举
// FileMode.CreateNew → 创建新文件,如果文件存在则抛出异常
// FileMode.Create → 创建/覆盖文件
// FileMode.Open → 打开现有文件,不存在则异常
// FileMode.OpenOrCreate → 打开或创建
// FileMode.Truncate → 打开并清空内容
// FileMode.Append → 追加到文件末尾
// 4. 使用 FileAccess 枚举
// FileAccess.Read → 只读
// FileAccess.Write → 只写
// FileAccess.ReadWrite → 读写
// 5. 使用 FileShare 枚举(控制其他进程访问)
// FileShare.None → 独占
// FileShare.Read → 允许其他进程读取
// FileShare.Write → 允许其他进程写入
// FileShare.ReadWrite → 允许其他进程读写
// FileShare.Delete → 允许其他进程删除
// 6. 异步读写
using (FileStream fs = new FileStream("async.bin", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true))
{
byte[] data = Encoding.UTF8.GetBytes("异步写入测试");
await fs.WriteAsync(data, 0, data.Length);
}
// 7. 设置位置
using (FileStream fs = new FileStream("data.bin", FileMode.Open))
{
long position = fs.Position; // 当前位置
long length = fs.Length; // 文件长度
// 跳转到指定位置
fs.Seek(2, SeekOrigin.Begin); // 从开头跳过2字节
fs.Seek(5, SeekOrigin.Current); // 从当前位置前进5字节
fs.Seek(-3, SeekOrigin.End); // 从末尾后退3字节
// 读取当前位置的数据
int b = fs.ReadByte();
}
}
}
13.5.3 MemoryStream------内存流
csharp
class MemoryStreamDemo
{
static void Main()
{
// 1. 创建内存流
byte[] data = { 1, 2, 3, 4, 5 };
using (MemoryStream ms = new MemoryStream(data))
{
// 读取
byte[] buffer = new byte[3];
int read = ms.Read(buffer, 0, 3);
Console.WriteLine($"读取了 {read} 字节");
}
// 2. 动态构建内存流
using (MemoryStream ms = new MemoryStream())
{
// 写入
byte[] bytes = Encoding.UTF8.GetBytes("Hello MemoryStream");
ms.Write(bytes, 0, bytes.Length);
// 获取数据
byte[] output = ms.ToArray();
Console.WriteLine($"内存流大小:{output.Length} 字节");
// 获取字符串
ms.Position = 0; // 重置位置
using (StreamReader reader = new StreamReader(ms))
{
string content = reader.ReadToEnd();
Console.WriteLine($"内容:{content}");
}
}
// 3. 使用 MemoryStream 进行序列化
MemoryStream ms2 = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms2);
writer.Write(123);
writer.Write("测试数据");
writer.Write(3.14);
// 重置位置准备读取
ms2.Position = 0;
BinaryReader reader2 = new BinaryReader(ms2);
int num = reader2.ReadInt32();
string str = reader2.ReadString();
double d = reader2.ReadDouble();
Console.WriteLine($"读取:{num}, {str}, {d}");
}
}
13.6 文本文件读写(StreamReader/StreamWriter)
13.6.1 StreamReader------读取文本文件
csharp
class StreamReaderDemo
{
static void Main()
{
string filePath = "sample.txt";
// 1. 创建文本内容
File.WriteAllLines(filePath, new[] { "第一行", "第二行", "第三行", "第四行", "第五行" });
// 2. 基础读取
using (StreamReader reader = new StreamReader(filePath))
{
// 读取所有内容
string allContent = reader.ReadToEnd();
Console.WriteLine(allContent);
}
// 3. 逐行读取
using (StreamReader reader = new StreamReader(filePath))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine($"行:{line}");
}
}
// 4. 指定编码读取
using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8))
{
string content = reader.ReadToEnd();
}
// 5. 检测编码自动读取
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (StreamReader reader = new StreamReader(fs, detectEncodingFromByteOrderMarks: true))
{
string content = reader.ReadToEnd();
Console.WriteLine($"编码:{reader.CurrentEncoding}");
}
}
// 6. 读取指定数量的字符
using (StreamReader reader = new StreamReader(filePath))
{
char[] buffer = new char[10];
int charsRead = reader.Read(buffer, 0, 10);
Console.WriteLine($"读取了 {charsRead} 个字符:{new string(buffer, 0, charsRead)}");
}
// 7. 异步读取
using (StreamReader reader = new StreamReader(filePath))
{
string content = reader.ReadToEndAsync().Result;
}
// 8. Peek 方法(查看下一个字符但不读取)
using (StreamReader reader = new StreamReader(filePath))
{
while (reader.Peek() >= 0)
{
char c = (char)reader.Read();
Console.Write(c);
}
}
// 9. 读取一行(异步)
using (StreamReader reader = new StreamReader(filePath))
{
string line = reader.ReadLineAsync().Result;
}
}
}
13.6.2 StreamWriter------写入文本文件
csharp
class StreamWriterDemo
{
static void Main()
{
string filePath = "output.txt";
// 1. 基本写入
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine("Hello, World!");
writer.WriteLine("第二行");
writer.Write("不换行的内容");
writer.Write("连续写入");
}
// 2. 指定编码写入
using (StreamWriter writer = new StreamWriter(filePath, append: false, encoding: Encoding.UTF8))
{
writer.WriteLine("UTF-8 编码的内容");
}
// 3. 追加模式
using (StreamWriter writer = new StreamWriter(filePath, append: true))
{
writer.WriteLine("追加的这一行");
}
// 4. 自动刷新
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.AutoFlush = true; // 每次 WriteLine 后立即写入
writer.WriteLine("立即写入");
}
// 5. 写入格式化内容
using (StreamWriter writer = new StreamWriter(filePath))
{
string name = "张三";
int age = 25;
double salary = 5000.50;
writer.WriteLine($"姓名:{name},年龄:{age},工资:{salary:C}");
writer.WriteLine("姓名:{0},年龄:{1},工资:{2:C}", name, age, salary);
}
// 6. 使用 using 声明(C# 8.0+)
using var writer2 = new StreamWriter(filePath);
writer2.WriteLine("简化的 using 语法");
// 7. 批量写入
using (StreamWriter writer = new StreamWriter(filePath))
{
var lines = new List<string> { "行1", "行2", "行3" };
foreach (var line in lines)
{
writer.WriteLine(line);
}
}
// 8. 异步写入
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLineAsync("异步写入").Wait();
}
// 9. 设置缓冲区大小
using (StreamWriter writer = new StreamWriter(filePath, append: false, Encoding.UTF8, bufferSize: 8192))
{
writer.WriteLine("使用 8KB 缓冲区");
}
}
}
13.6.3 高效处理大文件
csharp
class LargeFileProcessor
{
// 逐行处理大文件(不一次性加载到内存)
static void ProcessLargeFile(string inputPath, string outputPath)
{
const int batchSize = 10000;
List<string> batch = new List<string>(batchSize);
int lineCount = 0;
using (StreamReader reader = new StreamReader(inputPath))
using (StreamWriter writer = new StreamWriter(outputPath))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// 处理每一行
string processed = ProcessLine(line);
batch.Add(processed);
lineCount++;
// 批量写入
if (batch.Count >= batchSize)
{
writer.WriteLine(string.Join(Environment.NewLine, batch));
batch.Clear();
Console.WriteLine($"已处理 {lineCount} 行");
}
}
// 写入剩余行
if (batch.Count > 0)
{
writer.WriteLine(string.Join(Environment.NewLine, batch));
}
}
Console.WriteLine($"处理完成,共 {lineCount} 行");
}
static string ProcessLine(string line)
{
// 模拟处理逻辑
return line.ToUpper();
}
// 使用缓冲区提高性能
static void ProcessWithBuffer(string inputPath, string outputPath)
{
using (FileStream inputFs = new FileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192))
using (FileStream outputFs = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192))
using (StreamReader reader = new StreamReader(inputFs))
using (StreamWriter writer = new StreamWriter(outputFs))
{
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line.ToUpper());
}
}
}
// 并行处理大文件
static void ProcessParallel(string inputPath, string outputPath)
{
var lines = File.ReadLines(inputPath);
var processed = lines.AsParallel()
.Select(line => ProcessLineParallel(line))
.ToList();
File.WriteAllLines(outputPath, processed);
}
static string ProcessLineParallel(string line)
{
// 线程安全的处理逻辑
return line.ToUpper();
}
}
13.7 二进制文件读写
13.7.1 BinaryWriter 和 BinaryReader
csharp
class BinaryFileDemo
{
static void Main()
{
string filePath = "data.bin";
// 1. 写入二进制数据
using (FileStream fs = new FileStream(filePath, FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fs))
{
writer.Write(123); // int
writer.Write(3.14159); // double
writer.Write(true); // bool
writer.Write("Hello Binary"); // string
writer.Write((byte)255); // byte
writer.Write(1000L); // long
writer.Write('A'); // char
writer.Write(3.14f); // float
Console.WriteLine("二进制数据写入完成");
}
// 2. 读取二进制数据(必须按写入顺序读取)
using (FileStream fs = new FileStream(filePath, FileMode.Open))
using (BinaryReader reader = new BinaryReader(fs))
{
int intValue = reader.ReadInt32();
double doubleValue = reader.ReadDouble();
bool boolValue = reader.ReadBoolean();
string stringValue = reader.ReadString();
byte byteValue = reader.ReadByte();
long longValue = reader.ReadInt64();
char charValue = reader.ReadChar();
float floatValue = reader.ReadSingle();
Console.WriteLine($"int: {intValue}");
Console.WriteLine($"double: {doubleValue}");
Console.WriteLine($"bool: {boolValue}");
Console.WriteLine($"string: {stringValue}");
Console.WriteLine($"byte: {byteValue}");
Console.WriteLine($"long: {longValue}");
Console.WriteLine($"char: {charValue}");
Console.WriteLine($"float: {floatValue}");
}
// 3. 写入自定义结构体
var persons = new List<Person>
{
new Person { Id = 1, Name = "张三", Age = 25, Salary = 5000.50 },
new Person { Id = 2, Name = "李四", Age = 30, Salary = 6000.00 },
new Person { Id = 3, Name = "王五", Age = 28, Salary = 5500.75 }
};
using (FileStream fs = new FileStream("persons.bin", FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fs))
{
// 写入数量
writer.Write(persons.Count);
foreach (var p in persons)
{
writer.Write(p.Id);
writer.Write(p.Name);
writer.Write(p.Age);
writer.Write(p.Salary);
}
}
// 4. 读取自定义结构体
using (FileStream fs = new FileStream("persons.bin", FileMode.Open))
using (BinaryReader reader = new BinaryReader(fs))
{
int count = reader.ReadInt32();
List<Person> loaded = new List<Person>(count);
for (int i = 0; i < count; i++)
{
Person p = new Person
{
Id = reader.ReadInt32(),
Name = reader.ReadString(),
Age = reader.ReadInt32(),
Salary = reader.ReadDouble()
};
loaded.Add(p);
}
foreach (var p in loaded)
{
Console.WriteLine($"{p.Id}: {p.Name}, {p.Age}岁, {p.Salary:C}");
}
}
}
struct Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public double Salary { get; set; }
}
}
13.7.2 高级二进制操作
csharp
class AdvancedBinaryDemo
{
// 使用 MemoryMappedFile 处理超大文件(不加载到内存)
static void ProcessLargeBinaryFile(string filePath)
{
const long fileSize = 1024 * 1024 * 100; // 100MB
// 创建内存映射文件
using (var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.OpenOrCreate, "map", fileSize))
using (var accessor = mmf.CreateViewAccessor())
{
// 在指定位置写入数据
accessor.Write(0, 12345); // 在偏移 0 写入 int
accessor.Write(8, 3.14159); // 在偏移 8 写入 double
accessor.Write(16, (byte)255); // 在偏移 16 写入 byte
// 读取数据
int intValue = accessor.ReadInt32(0);
double doubleValue = accessor.ReadDouble(8);
byte byteValue = accessor.ReadByte(16);
Console.WriteLine($"读取:{intValue}, {doubleValue}, {byteValue}");
}
}
// 使用 BitConverter 转换字节
static void BitConverterDemo()
{
int number = 12345678;
byte[] bytes = BitConverter.GetBytes(number);
Console.WriteLine($"int {number} 的字节表示:{BitConverter.ToString(bytes)}");
int restored = BitConverter.ToInt32(bytes, 0);
Console.WriteLine($"还原:{restored}");
// 检查系统字节序
if (BitConverter.IsLittleEndian)
Console.WriteLine("当前系统是小端序");
else
Console.WriteLine("当前系统是大端序");
}
// 读取文件的部分内容
static void ReadPartialFile(string filePath)
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
// 读取文件头(前 100 字节)
byte[] header = new byte[100];
int read = fs.Read(header, 0, header.Length);
// 跳转到偏移量 1000
fs.Seek(1000, SeekOrigin.Begin);
// 读取后续数据
byte[] data = new byte[4096];
read = fs.Read(data, 0, data.Length);
}
}
}
13.8 异步文件操作
13.8.1 异步读写
csharp
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class AsyncFileDemo
{
static async Task Main()
{
Console.WriteLine("=== 异步文件操作演示 ===");
string filePath = "async_test.txt";
// 1. 异步写入 await WriteFileAsync(filePath, "这是异步写入的内容");
Console.WriteLine("异步写入完成");
// 2. 异步读取
string content = await ReadFileAsync(filePath);
Console.WriteLine($"读取内容:{content}");
// 3. 批量异步操作
await ProcessMultipleFiles();
// 4. 异步文件复制
await CopyFileAsync("source.txt", "dest.txt");
}
static async Task WriteFileAsync(string path, string content)
{
using (StreamWriter writer = new StreamWriter(path))
{
await writer.WriteAsync(content);
}
}
static async Task<string> ReadFileAsync(string path)
{
using (StreamReader reader = new StreamReader(path))
{
return await reader.ReadToEndAsync();
}
}
static async Task ProcessMultipleFiles()
{
string[] files = { "file1.txt", "file2.txt", "file3.txt" };
var tasks = new List<Task>();
foreach (string file in files)
{
tasks.Add(Task.Run(async () =>
{
await WriteFileAsync(file, $"内容 for {file}");
Console.WriteLine($"完成:{file}");
}));
}
await Task.WhenAll(tasks);
Console.WriteLine("所有文件处理完成");
}
static async Task CopyFileAsync(string sourcePath, string destPath)
{
using (FileStream source = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true))
using (FileStream dest = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true))
{
await source.CopyToAsync(dest);
Console.WriteLine($"文件复制完成:{sourcePath} → {destPath}");
}
}
// 异步逐行处理大文件
static async Task ProcessLargeFileAsync(string inputPath, string outputPath)
{
using (StreamReader reader = new StreamReader(inputPath))
using (StreamWriter writer = new StreamWriter(outputPath))
{
string line;
int lineNumber = 0;
while ((line = await reader.ReadLineAsync()) != null)
{
lineNumber++;
string processed = await ProcessLineAsync(line);
await writer.WriteLineAsync(processed);
if (lineNumber % 1000 == 0)
{
Console.WriteLine($"已处理 {lineNumber} 行");
}
}
}
Console.WriteLine("异步处理完成");
}
static async Task<string> ProcessLineAsync(string line)
{
// 模拟异步处理
await Task.Delay(1);
return line.ToUpper();
}
}
13.8.2 使用 IAsyncEnumerable 处理大文件
csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
class AsyncEnumerableDemo
{
// 异步流式读取文件(.NET Core 3.0+)
static async IAsyncEnumerable<string> ReadLinesAsync(string path, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using (StreamReader reader = new StreamReader(path))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
yield return line;
}
}
}
static async Task ProcessLinesAsync()
{
string filePath = "largefile.txt";
int processedCount = 0;
await foreach (string line in ReadLinesAsync(filePath))
{
// 处理每一行
string processed = ProcessLine(line);
processedCount++;
if (processedCount % 1000 == 0)
{
Console.WriteLine($"已处理 {processedCount} 行");
}
}
Console.WriteLine($"处理完成,共 {processedCount} 行");
}
static string ProcessLine(string line) => line.ToUpper();
}
13.9 压缩与解压缩
13.9.1 使用 GZipStream
csharp
using System.IO.Compression;
class CompressionDemo
{
static void Main()
{
string originalFile = "data.txt";
string compressedFile = "data.gz";
string decompressedFile = "decompressed.txt";
// 创建测试文件
string content = new string('A', 10000) + new string('B', 10000);
File.WriteAllText(originalFile, content);
// 压缩文件
CompressFile(originalFile, compressedFile);
Console.WriteLine($"压缩完成:{originalFile} → {compressedFile}");
// 显示压缩效果
long originalSize = new FileInfo(originalFile).Length;
long compressedSize = new FileInfo(compressedFile).Length;
double ratio = (double)compressedSize / originalSize * 100;
Console.WriteLine($"原大小:{originalSize} 字节,压缩后:{compressedSize} 字节,比例:{ratio:F1}%");
// 解压缩
DecompressFile(compressedFile, decompressedFile);
Console.WriteLine($"解压完成:{compressedFile} → {decompressedFile}");
// 验证内容一致
string originalContent = File.ReadAllText(originalFile);
string decompressedContent = File.ReadAllText(decompressedFile);
Console.WriteLine($"内容一致:{originalContent == decompressedContent}");
}
static void CompressFile(string sourcePath, string destPath)
{
using (FileStream source = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
using (FileStream dest = new FileStream(destPath, FileMode.Create, FileAccess.Write))
using (GZipStream gzip = new GZipStream(dest, CompressionMode.Compress))
{
source.CopyTo(gzip);
}
}
static void DecompressFile(string sourcePath, string destPath)
{
using (FileStream source = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
using (FileStream dest = new FileStream(destPath, FileMode.Create, FileAccess.Write))
using (GZipStream gzip = new GZipStream(source, CompressionMode.Decompress))
{
gzip.CopyTo(dest);
}
}
// 内存中压缩
static byte[] CompressString(string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
using (MemoryStream ms = new MemoryStream())
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress))
{
gzip.Write(bytes, 0, bytes.Length);
gzip.Close();
return ms.ToArray();
}
}
static string DecompressString(byte[] compressed)
{
using (MemoryStream ms = new MemoryStream(compressed))
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress))
using (StreamReader reader = new StreamReader(gzip))
{
return reader.ReadToEnd();
}
}
}
13.9.2 ZipArchive 操作
csharp
using System.IO.Compression;
class ZipArchiveDemo
{
static void Main()
{
string zipPath = "archive.zip";
string extractPath = "extracted";
// 1. 创建 ZIP 文件
using (FileStream fs = new FileStream(zipPath, FileMode.Create))
using (ZipArchive archive = new ZipArchive(fs, ZipArchiveMode.Create))
{
// 添加文件
ZipArchiveEntry entry1 = archive.CreateEntry("file1.txt");
using (StreamWriter writer = new StreamWriter(entry1.Open()))
{
writer.WriteLine("这是第一个文件的内容");
}
ZipArchiveEntry entry2 = archive.CreateEntry("subfolder/file2.txt");
using (StreamWriter writer = new StreamWriter(entry2.Open()))
{
writer.WriteLine("这是子文件夹中的文件");
}
// 从现有文件添加
ZipArchiveEntry entry3 = archive.CreateEntry("data.json");
using (Stream entryStream = entry3.Open())
using (FileStream fileStream = File.OpenRead("config.json"))
{
fileStream.CopyTo(entryStream);
}
Console.WriteLine("ZIP 文件创建完成");
}
// 2. 读取 ZIP 文件
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
Console.WriteLine($"文件:{entry.FullName}");
Console.WriteLine($" 大小:{entry.Length} 字节");
Console.WriteLine($" 压缩后:{entry.CompressedLength} 字节");
Console.WriteLine($" 最后修改:{entry.LastWriteTime}");
// 读取文件内容
using (StreamReader reader = new StreamReader(entry.Open()))
{
string content = reader.ReadToEnd();
Console.WriteLine($" 内容预览:{content.Substring(0, Math.Min(50, content.Length))}");
}
}
}
// 3. 解压缩
ZipFile.ExtractToDirectory(zipPath, extractPath, overwriteFiles: true);
Console.WriteLine($"解压完成到:{extractPath}");
// 4. 使用 ZipFile 快速创建
string tempZip = "quick.zip";
ZipFile.CreateFromDirectory("sourceFolder", tempZip);
// 5. 添加注释
using (FileStream fs = new FileStream(zipPath, FileMode.Open))
using (ZipArchive archive = new ZipArchive(fs, ZipArchiveMode.Update))
{
// 添加注释(需要额外的库或自定义实现)
}
}
// 递归压缩目录
static void CompressDirectory(string sourceDir, string zipPath)
{
using (FileStream fs = new FileStream(zipPath, FileMode.Create))
using (ZipArchive archive = new ZipArchive(fs, ZipArchiveMode.Create))
{
CompressDirectoryRecursive(archive, sourceDir, "");
}
}
static void CompressDirectoryRecursive(ZipArchive archive, string directory, string entryPrefix)
{
foreach (string file in Directory.GetFiles(directory))
{
string entryName = Path.Combine(entryPrefix, Path.GetFileName(file));
ZipArchiveEntry entry = archive.CreateEntry(entryName);
using (FileStream fileStream = File.OpenRead(file))
using (Stream entryStream = entry.Open())
{
fileStream.CopyTo(entryStream);
}
}
foreach (string subDir in Directory.GetDirectories(directory))
{
string subDirName = Path.GetFileName(subDir);
string newPrefix = Path.Combine(entryPrefix, subDirName);
CompressDirectoryRecursive(archive, subDir, newPrefix);
}
}
}
13.10 综合示例
示例1:日志文件管理系统
csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
class LogEntry
{
public DateTime Timestamp { get; set; }
public string Level { get; set; }
public string Message { get; set; }
public string Source { get; set; }
public Exception Exception { get; set; }
public override string ToString()
{
string log = $"{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Source}: {Message}";
if (Exception != null)
{
log += $"\n 异常:{Exception.Message}\n 堆栈:{Exception.StackTrace}";
}
return log;
}
public static LogEntry Parse(string line)
{
try
{
// 格式:2024-01-15 10:30:45.123 [INFO] Source: Message
var parts = line.Split(new[] { " " }, 4);
if (parts.Length < 4) return null;
DateTime timestamp = DateTime.Parse(parts[0] + " " + parts[1]);
string level = parts[2].Trim('[', ']');
string[] msgParts = parts[3].Split(new[] { ": " }, 2);
string source = msgParts[0];
string message = msgParts.Length > 1 ? msgParts[1] : "";
return new LogEntry
{
Timestamp = timestamp,
Level = level,
Source = source,
Message = message
};
}
catch
{
return null;
}
}
}
class LogManager
{
private string logDirectory;
private string currentLogFile;
private StreamWriter writer;
private object lockObject = new object();
private int maxFileSizeMB = 10;
private int retentionDays = 30;
public LogManager(string logDirectory = "logs")
{
this.logDirectory = logDirectory;
Directory.CreateDirectory(logDirectory);
string today = DateTime.Now.ToString("yyyy-MM-dd");
currentLogFile = Path.Combine(logDirectory, $"log_{today}.txt");
// 打开日志文件(追加模式)
writer = new StreamWriter(currentLogFile, append: true, Encoding.UTF8);
writer.AutoFlush = true;
}
public void Info(string source, string message)
{
WriteLog(new LogEntry
{
Timestamp = DateTime.Now,
Level = "INFO",
Source = source,
Message = message
});
}
public void Warning(string source, string message)
{
WriteLog(new LogEntry
{
Timestamp = DateTime.Now,
Level = "WARN",
Source = source,
Message = message
});
}
public void Error(string source, string message, Exception ex = null)
{
WriteLog(new LogEntry
{
Timestamp = DateTime.Now,
Level = "ERROR",
Source = source,
Message = message,
Exception = ex
});
}
private void WriteLog(LogEntry entry)
{
lock (lockObject)
{
writer.WriteLine(entry.ToString());
// 检查是否需要滚动日志
CheckAndRollLog();
}
}
private void CheckAndRollLog()
{
FileInfo fi = new FileInfo(currentLogFile);
if (fi.Exists && fi.Length > maxFileSizeMB * 1024 * 1024)
{
// 关闭当前 writer
writer.Close();
// 重命名当前日志文件
string timestamp = DateTime.Now.ToString("HH-mm-ss");
string archiveFile = Path.Combine(logDirectory, $"log_{DateTime.Now:yyyy-MM-dd}_{timestamp}.txt");
File.Move(currentLogFile, archiveFile);
// 创建新日志文件
currentLogFile = Path.Combine(logDirectory, $"log_{DateTime.Now:yyyy-MM-dd}.txt");
writer = new StreamWriter(currentLogFile, append: true, Encoding.UTF8);
writer.AutoFlush = true;
}
}
public void CleanOldLogs()
{
DateTime cutoff = DateTime.Now.AddDays(-retentionDays);
var oldFiles = Directory.GetFiles(logDirectory, "log_*.txt")
.Where(f => File.GetCreationTime(f) < cutoff);
foreach (string file in oldFiles)
{
try
{
File.Delete(file);
Console.WriteLine($"已删除旧日志:{Path.GetFileName(file)}");
}
catch (Exception ex)
{
Console.WriteLine($"删除失败:{file}, {ex.Message}");
}
}
}
public List<LogEntry> SearchLogs(string keyword, DateTime? start = null, DateTime? end = null, string level = null)
{
List<LogEntry> results = new List<LogEntry>();
var files = Directory.GetFiles(logDirectory, "log_*.txt");
foreach (string file in files)
{
using (StreamReader reader = new StreamReader(file))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var entry = LogEntry.Parse(line);
if (entry == null) continue;
// 应用过滤条件
if (start.HasValue && entry.Timestamp < start.Value) continue;
if (end.HasValue && entry.Timestamp > end.Value) continue;
if (level != null && entry.Level != level) continue;
if (!string.IsNullOrEmpty(keyword) && !entry.Message.Contains(keyword)) continue;
results.Add(entry);
}
}
}
return results.OrderBy(e => e.Timestamp).ToList();
}
public void GenerateReport(string outputPath, DateTime date)
{
var logs = SearchLogs("", date.Date, date.Date.AddDays(1));
var stats = logs
.GroupBy(l => l.Level)
.Select(g => new { Level = g.Key, Count = g.Count() })
.ToDictionary(x => x.Level, x => x.Count);
using (StreamWriter writer = new StreamWriter(outputPath))
{
writer.WriteLine($"日志报告 - {date:yyyy-MM-dd}");
writer.WriteLine(new string('=', 50));
writer.WriteLine($"总日志数:{logs.Count}");
writer.WriteLine($"INFO: {stats.GetValueOrDefault("INFO", 0)}");
writer.WriteLine($"WARN: {stats.GetValueOrDefault("WARN", 0)}");
writer.WriteLine($"ERROR: {stats.GetValueOrDefault("ERROR", 0)}");
writer.WriteLine();
writer.WriteLine("详细日志:");
writer.WriteLine(new string('-', 50));
foreach (var log in logs)
{
writer.WriteLine(log.ToString());
writer.WriteLine();
}
}
}
public void Close()
{
writer?.Close();
}
}
// 使用示例
class Program
{
static void Main()
{
LogManager logger = new LogManager();
// 写入日志
logger.Info("Program", "程序启动");
logger.Warning("Program", "配置项缺失,使用默认值");
try
{
throw new InvalidOperationException("模拟异常");
}
catch (Exception ex)
{
logger.Error("BusinessModule", "处理数据时发生错误", ex);
}
logger.Info("Program", "程序结束");
// 搜索日志
var errors = logger.SearchLogs("错误", level: "ERROR");
Console.WriteLine($"找到 {errors.Count} 条错误日志");
// 生成报告
logger.GenerateReport("log_report.txt", DateTime.Now.Date);
// 清理旧日志
logger.CleanOldLogs();
logger.Close();
}
}
示例2:配置文件管理器
csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
// 配置文件基类
abstract class ConfigBase
{
public virtual void Validate() { }
}
// 应用配置
class AppConfig : ConfigBase
{
public DatabaseConfig Database { get; set; } = new DatabaseConfig();
public LoggingConfig Logging { get; set; } = new LoggingConfig();
public List<string> AdminUsers { get; set; } = new List<string>();
public int MaxRetryCount { get; set; } = 3;
public int TimeoutSeconds { get; set; } = 30;
public override void Validate()
{
if (string.IsNullOrEmpty(Database?.ConnectionString))
throw new ValidationException("数据库连接字符串不能为空");
if (TimeoutSeconds <= 0 || TimeoutSeconds > 300)
throw new ValidationException("超时时间必须在1-300秒之间");
if (MaxRetryCount < 0 || MaxRetryCount > 10)
throw new ValidationException("重试次数必须在0-10之间");
}
}
class DatabaseConfig
{
public string ConnectionString { get; set; } = "";
public string Provider { get; set; } = "SqlServer";
public int CommandTimeout { get; set; } = 30;
public bool EnablePooling { get; set; } = true;
public int MaxPoolSize { get; set; } = 100;
}
class LoggingConfig
{
public string LogLevel { get; set; } = "Info";
public string LogDirectory { get; set; } = "logs";
public int MaxFileSizeMB { get; set; } = 10;
public int RetentionDays { get; set; } = 30;
public bool EnableConsole { get; set; } = true;
}
class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}
// 配置文件管理器
class ConfigManager<T> where T : ConfigBase, new()
{
private string configPath;
private T config;
private FileSystemWatcher watcher;
private object lockObject = new object();
public event Action<T> ConfigChanged;
public ConfigManager(string configFileName = "appsettings.json")
{
configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configFileName);
LoadConfig();
StartWatching();
}
public T Config => config;
private void LoadConfig()
{
lock (lockObject)
{
try
{
if (File.Exists(configPath))
{
string json = File.ReadAllText(configPath);
config = JsonSerializer.Deserialize<T>(json) ?? new T();
Console.WriteLine($"配置文件加载成功:{configPath}");
}
else
{
config = new T();
SaveConfig();
Console.WriteLine($"配置文件不存在,已创建默认配置:{configPath}");
}
config.Validate();
}
catch (JsonException ex)
{
Console.WriteLine($"配置文件格式错误:{ex.Message}");
throw;
}
catch (ValidationException ex)
{
Console.WriteLine($"配置验证失败:{ex.Message}");
throw;
}
}
}
public void SaveConfig()
{
lock (lockObject)
{
config.Validate();
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string json = JsonSerializer.Serialize(config, options);
// 先写临时文件,再替换
string tempPath = configPath + ".tmp";
File.WriteAllText(tempPath, json);
File.Copy(tempPath, configPath, overwrite: true);
File.Delete(tempPath);
Console.WriteLine($"配置文件已保存:{configPath}");
}
}
private void StartWatching()
{
watcher = new FileSystemWatcher(Path.GetDirectoryName(configPath))
{
Filter = Path.GetFileName(configPath),
EnableRaisingEvents = true
};
watcher.Changed += OnConfigFileChanged;
watcher.Created += OnConfigFileChanged;
}
private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
{
// 延迟一下,避免文件还在写入
Thread.Sleep(100);
try
{
LoadConfig();
ConfigChanged?.Invoke(config);
Console.WriteLine("配置文件已重新加载");
}
catch (Exception ex)
{
Console.WriteLine($"配置文件重新加载失败:{ex.Message}");
}
}
public void Reload() => LoadConfig();
public void Backup()
{
string backupPath = configPath + $".{DateTime.Now:yyyyMMddHHmmss}.bak";
File.Copy(configPath, backupPath);
Console.WriteLine($"配置备份已创建:{backupPath}");
}
}
// 使用示例
class Program
{
static void Main()
{
Console.WriteLine("=== 配置管理器演示 ===\n");
// 创建配置管理器
ConfigManager<AppConfig> configManager = new ConfigManager<AppConfig>();
// 订阅配置变化事件
configManager.ConfigChanged += newConfig =>
{
Console.WriteLine("\n[配置已更新]");
DisplayConfig(newConfig);
};
// 显示当前配置
DisplayConfig(configManager.Config);
// 修改配置
Console.WriteLine("\n修改配置...");
configManager.Config.MaxRetryCount = 5;
configManager.Config.TimeoutSeconds = 60;
configManager.Config.AdminUsers.Add("admin");
configManager.Config.Database.ConnectionString = "Server=localhost;Database=MyDB;";
// 保存配置
configManager.SaveConfig();
// 演示备份
configManager.Backup();
// 等待文件监控(如果有外部修改)
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
static void DisplayConfig(AppConfig config)
{
Console.WriteLine("=== 当前配置 ===");
Console.WriteLine($"数据库连接:{config.Database.ConnectionString}");
Console.WriteLine($"超时时间:{config.TimeoutSeconds}秒");
Console.WriteLine($"最大重试次数:{config.MaxRetryCount}");
Console.WriteLine($"管理员用户:{string.Join(", ", config.AdminUsers)}");
Console.WriteLine($"日志级别:{config.Logging.LogLevel}");
Console.WriteLine($"日志目录:{config.Logging.LogDirectory}");
}
}
13.11 常见错误与陷阱
错误1:忘记释放资源
csharp
// ❌ 错误:没有释放 StreamReader
StreamReader reader = new StreamReader("file.txt");
string content = reader.ReadToEnd();
// reader 未关闭,文件被锁定
// ✅ 正确:使用 using
using (StreamReader reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
}
错误2:路径分隔符硬编码
csharp
// ❌ 错误:硬编码 Windows 路径分隔符
string path = "C:\\folder\\file.txt";
// ✅ 正确:使用 Path.Combine
string path = Path.Combine("C:", "folder", "file.txt");
错误3:文件被占用时访问
csharp
// ❌ 错误:没有处理文件被占用的情况
string content = File.ReadAllText("file.txt"); // 如果文件被其他进程打开会抛异常
// ✅ 正确:使用适当的 FileShare
using (FileStream fs = new FileStream("file.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader reader = new StreamReader(fs))
{
string content = reader.ReadToEnd();
}
错误4:编码问题
csharp
// ❌ 错误:写入和读取使用不同编码
File.WriteAllText("file.txt", "中文"); // 默认 UTF-8
string content = File.ReadAllText("file.txt", Encoding.Default); // 可能乱码
// ✅ 正确:明确指定编码
File.WriteAllText("file.txt", "中文", Encoding.UTF8);
string content = File.ReadAllText("file.txt", Encoding.UTF8);
13.12 本章总结
核心知识点导图
text
文件与流 I/O
├── 路径处理(Path)
├── 文件和目录操作
│ ├── File / FileInfo
│ └── Directory / DirectoryInfo
├── 流操作
│ ├── FileStream(字节级)
│ ├── StreamReader/Writer(文本)
│ ├── BinaryReader/Writer(二进制)
│ └── MemoryStream(内存)
├── 高级操作
│ ├── 异步 I/O
│ ├── 压缩(GZipStream, ZipArchive)
│ └── 内存映射文件
└── 最佳实践
├── 使用 using 释放资源
├── 指定正确的编码
├── 处理异常
└── 使用缓冲区提高性能
选择指南
| 场景 | 推荐类 |
|---|---|
| 简单文本读写 | File.ReadAllText / File.WriteAllText |
| 逐行处理大文件 | StreamReader / StreamWriter |
| 二进制数据 | BinaryReader / BinaryWriter |
| 需要随机访问 | FileStream + Seek |
| 内存中处理 | MemoryStream |
| 压缩数据 | GZipStream / ZipArchive |
| 异步操作 | ReadAsync / WriteAsync |
13.13 练习题
基础题
-
编写程序递归遍历指定目录,输出所有文件的大小和最后修改时间。
-
实现一个简单的文本文件搜索工具,在指定目录中搜索包含关键词的文本文件。
-
使用
BinaryWriter和BinaryReader实现一个学生信息存储系统。
应用题
-
实现一个文件分片器:
-
将大文件分割成指定大小的多个小文件
-
实现合并功能
-
添加校验和验证
-
-
实现一个配置文件监听器:
-
监控配置文件的变化
-
自动重新加载配置
-
通知订阅者
-
挑战题
-
实现一个简单的版本控制系统:
-
记录文件的修改历史
-
支持回滚到任意版本
-
支持查看差异
-
-
实现一个日志分析器:
-
支持多种日志格式
-
统计错误率、响应时间等指标
-
生成 HTML 报告
-