C# 零基础到精通教程 - 第十三章:文件与流 I/O——读写文件

第十二章我们学习了异常处理和调试,知道了如何让程序更健壮。但程序运行时的数据存储在内存中,一旦程序关闭就会丢失。这一章我们要学习文件 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 练习题

基础题

  1. 编写程序递归遍历指定目录,输出所有文件的大小和最后修改时间。

  2. 实现一个简单的文本文件搜索工具,在指定目录中搜索包含关键词的文本文件。

  3. 使用 BinaryWriterBinaryReader 实现一个学生信息存储系统。

应用题

  1. 实现一个文件分片器:

    • 将大文件分割成指定大小的多个小文件

    • 实现合并功能

    • 添加校验和验证

  2. 实现一个配置文件监听器:

    • 监控配置文件的变化

    • 自动重新加载配置

    • 通知订阅者

挑战题

  1. 实现一个简单的版本控制系统:

    • 记录文件的修改历史

    • 支持回滚到任意版本

    • 支持查看差异

  2. 实现一个日志分析器:

    • 支持多种日志格式

    • 统计错误率、响应时间等指标

    • 生成 HTML 报告

相关推荐
神仙别闹2 分钟前
基于C#实现(WinForm)求解SIN(X)数值分析
c#
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
skywalk81634 小时前
言知(Yanzhi)系统提升建议报告和完工报告 by AutoCoder
开发语言·编程
yunn_4 小时前
单例模式两种实现方法
开发语言·c++·单例模式
我材不敲代码4 小时前
Python基础:列表详解、增删改查及常用高阶操作
开发语言·windows·python
AI玫瑰助手4 小时前
Python运算符:成员运算符(in/not in)的使用场景
开发语言·python·信息可视化
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试
水木流年追梦5 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt
口袋里のInit5 小时前
基础知识——ARM M核入栈出栈流程
开发语言·arm开发