C# 本地数据交互详解 —— 读取各类文件
一、概述——C# 怎么读写文件?
1.1 C# 文件操作的核心类
所有文件读写都在 System.IO 命名空间下。核心类家族:
| 类 | 用途 | 特点 |
|---|---|---|
File |
静态方法,一次性读写 | 最简单,适合小文件 |
FileStream |
流式读写 | 灵活,控制每个字节 |
StreamReader / StreamWriter |
读写文本文件 | 逐行处理,适合大文件 |
BinaryReader / BinaryWriter |
读写二进制文件 | 按数据类型读取 |
FileInfo |
文件和目录信息 | 获取大小、日期等属性 |
1.2 本篇要讲的内容
📝 TXT 文件 → 用 File、StreamReader(C# 自带,不需要装包)
📊 Excel 文件 → 用 ClosedXML(第三方库,NuGet 安装)
💾 二进制文件 → 用 FileStream、BinaryReader(C# 自带)
📄 PDF 文件 → 用 PdfPig 或 iTextSharp(第三方库,NuGet 安装)
二、读取 TXT 文本文件
2.1 准备一个测试文件
先在项目目录下创建一个 data.txt:
张三,18,92
李四,19,85
王五,18,76
赵六,20,58
孙七,19,88
2.2 方法一:File.ReadAllText —— 一次性读全部(最简单)
using System;
using System.IO;
class Program
{
static void Main()
{
string path = "data.txt";
// 检查文件是否存在
if (!File.Exists(path))
{
Console.WriteLine($"文件 {path} 不存在!");
return;
}
// 一次性读取全部内容
string allText = File.ReadAllText(path);
Console.WriteLine("===== 文件全部内容 =====");
Console.WriteLine(allText);
}
}
输出:
===== 文件全部内容 =====
张三,18,92
李四,19,85
王五,18,76
赵六,20,58
孙七,19,88
2.3 方法二:File.ReadAllLines —— 逐行读取(返回数组)
string[] lines = File.ReadAllLines("data.txt");
Console.WriteLine("===== 逐行读取 =====");
for (int i = 0; i < lines.Length; i++)
{
Console.WriteLine($"第{i + 1}行: {lines[i]}");
// 按逗号拆分,解析每列数据
string[] parts = lines[i].Split(',');
string name = parts[0];
int age = int.Parse(parts[1]);
int score = int.Parse(parts[2]);
Console.WriteLine($" → 姓名:{name}, 年龄:{age}, 分数:{score}");
}
输出:
===== 逐行读取 =====
第1行: 张三,18,92
→ 姓名:张三, 年龄:18, 分数:92
第2行: 李四,19,85
→ 姓名:李四, 年龄:19, 分数:85
...
适用场景:
File.ReadAllLines适合小文件(几十 MB 以内),一行一个字符串数组,遍历处理很方便。
2.4 方法三:StreamReader —— 逐行读取(适合大文件)
// StreamReader 一次只读一行,内存友好,适合大文件
using (StreamReader reader = new StreamReader("data.txt"))
{
Console.WriteLine("===== StreamReader 逐行读取 =====");
string line;
int lineNum = 0;
while ((line = reader.ReadLine()) != null) // 读到 null 表示文件结束
{
lineNum++;
Console.WriteLine($"第{lineNum}行: {line}");
}
Console.WriteLine($"共读取 {lineNum} 行");
}
三种读取方式对比:
| 方式 | 方法 | 适用场景 | 内存占用 |
|---|---|---|---|
File.ReadAllText |
一次性读全部文本 | 小文件(<10MB) | 大 |
File.ReadAllLines |
一次性读全部行 | 小文件(<50MB) | 较大 |
StreamReader |
一行一行读 | 大文件(几百MB~GB) | 小 |
2.5 完整示例——读取并解析 CSV 数据
using System;
using System.Collections.Generic;
using System.IO;
public class Student
{
public string Name;
public int Age;
public int Score;
public override string ToString() => $"{Name,-6} | {Age}岁 | {Score}分";
}
class Program
{
static void Main()
{
Console.WriteLine("===== 读取并解析 data.txt =====\n");
List<Student> students = new List<Student>();
using (StreamReader reader = new StreamReader("data.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// 跳过空行
if (string.IsNullOrWhiteSpace(line)) continue;
string[] parts = line.Split(',');
if (parts.Length < 3) continue; // 跳过格式不对的行
students.Add(new Student
{
Name = parts[0],
Age = int.Parse(parts[1]),
Score = int.Parse(parts[2])
});
}
}
Console.WriteLine($"共读取 {students.Count} 条记录\n");
// 分析数据
Console.WriteLine("【学生列表】");
foreach (var s in students)
Console.WriteLine($" {s}");
Console.WriteLine($"\n【统计】");
Console.WriteLine($" 平均分: {students.Average(s => s.Score):F1}");
Console.WriteLine($" 最高分: {students.Max(s => s.Score)}");
Console.WriteLine($" 及格率: {(double)students.Count(s => s.Score >= 60) / students.Count * 100:F1}%");
}
}
2.6 写入 TXT 文件
// 写入文本
File.WriteAllText("output.txt", "Hello World!这是写入的内容。");
// 写入多行
string[] lines = { "第一行", "第二行", "第三行" };
File.WriteAllLines("output.txt", lines);
// 追加内容(不覆盖原内容)
File.AppendAllText("output.txt", "\n这是追加的一行");
三、读取二进制文件
3.1 什么是二进制文件?
二进制文件就是数据以字节形式直接存储,不是可读的文本。比如:
- 图片文件(.jpg、.png)
- 音频文件(.mp3、.wav)
- 可执行文件(.exe、.dll)
- 自定义的数据文件
3.2 读取二进制数据 —— FileStream + BinaryReader
using System;
using System.IO;
class Program
{
static void Main()
{
string path = "data.bin";
// ===== 第一步:写入一个二进制文件(演示用) =====
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
{
writer.Write(42); // 写 int
writer.Write(3.14159); // 写 double
writer.Write("Hello Binary!"); // 写 string
writer.Write(true); // 写 bool
writer.Write(1234567890123L); // 写 long
}
Console.WriteLine("二进制文件已创建");
// ===== 第二步:读取二进制文件 =====
using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))
{
int intValue = reader.ReadInt32(); // 读 int
double doubleValue = reader.ReadDouble(); // 读 double
string stringValue = reader.ReadString(); // 读 string
bool boolValue = reader.ReadBoolean(); // 读 bool
long longValue = reader.ReadInt64(); // 读 long
Console.WriteLine($"int: {intValue}");
Console.WriteLine($"double: {doubleValue}");
Console.WriteLine($"string: {stringValue}");
Console.WriteLine($"bool: {boolValue}");
Console.WriteLine($"long: {longValue}");
}
}
}
输出:
二进制文件已创建
int: 42
double: 3.14159
string: Hello Binary!
bool: True
long: 1234567890123
关键规则:二进制文件读的顺序必须和写的顺序完全一致,否则会读到错误的数据!
3.3 按字节读取 —— FileStream 底层操作
// 适合复制文件、处理图片等场景
using (FileStream fs = new FileStream("source.jpg", FileMode.Open))
{
// 读取文件大小
long fileSize = fs.Length;
Console.WriteLine($"文件大小: {fileSize} 字节 ({fileSize / 1024.0:F1} KB)");
// 一次性读取全部字节
byte[] buffer = new byte[fileSize];
int bytesRead = fs.Read(buffer, 0, buffer.Length);
Console.WriteLine($"实际读取: {bytesRead} 字节");
// 可以查看前几个字节(文件头/魔数)
Console.Write("前 8 字节: ");
for (int i = 0; i < Math.Min(8, bytesRead); i++)
{
Console.Write($"{buffer[i]:X2} "); // 16 进制显示
}
Console.WriteLine();
}
3.4 分块读取大文件
// 大文件不要一次性读完,分块处理
using (FileStream fs = new FileStream("largefile.dat", FileMode.Open))
{
byte[] buffer = new byte[4096]; // 每次读 4KB
int bytesRead;
long totalRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
totalRead += bytesRead;
// 处理这 bytesRead 个字节...
Console.Write($"\r已读取: {totalRead} 字节");
}
Console.WriteLine($"\n读取完成,共 {totalRead} 字节");
}
3.5 完整示例——学生数据的二进制存储
using System;
using System.Collections.Generic;
using System.IO;
public class Student
{
public string Name;
public int Age;
public int Score;
}
class Program
{
// 保存学生列表为二进制文件
static void SaveStudents(string path, List<Student> students)
{
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
{
writer.Write(students.Count); // 先写数量
foreach (var s in students)
{
writer.Write(s.Name);
writer.Write(s.Age);
writer.Write(s.Score);
}
}
Console.WriteLine($"保存完成: {students.Count} 条记录");
}
// 从二进制文件读取学生列表
static List<Student> LoadStudents(string path)
{
List<Student> students = new List<Student>();
using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))
{
int count = reader.ReadInt32(); // 先读数量
for (int i = 0; i < count; i++)
{
students.Add(new Student
{
Name = reader.ReadString(),
Age = reader.ReadInt32(),
Score = reader.ReadInt32()
});
}
}
return students;
}
static void Main()
{
// 创建数据
var students = new List<Student>
{
new Student { Name = "张三", Age = 18, Score = 92 },
new Student { Name = "李四", Age = 19, Score = 85 },
new Student { Name = "王五", Age = 18, Score = 76 },
};
// 保存
SaveStudents("students.bin", students);
// 读取
var loaded = LoadStudents("students.bin");
Console.WriteLine($"\n读取到 {loaded.Count} 条记录:");
foreach (var s in loaded)
Console.WriteLine($" {s.Name} - {s.Age}岁 - {s.Score}分");
}
}
四、读取 Excel 表格
4.1 需要安装的包
C# 本身不支持 Excel,需要用第三方库。推荐 ClosedXML(免费、开源、支持 .xlsx):
# 在 NuGet 包管理器控制台执行:
Install-Package ClosedXML
4.2 准备一个 Excel 文件
创建一个 students.xlsx,内容如下:
| 姓名 | 年龄 | 分数 |
|---|---|---|
| 张三 | 18 | 92 |
| 李四 | 19 | 85 |
| 王五 | 18 | 76 |
| 赵六 | 20 | 58 |
| 孙七 | 19 | 88 |
4.3 读取 Excel —— 基本操作
using System;
using ClosedXML.Excel;
class Program
{
static void Main()
{
string path = "students.xlsx";
using (var workbook = new XLWorkbook(path)) // 打开工作簿
{
var worksheet = workbook.Worksheet(1); // 获取第一个工作表
Console.WriteLine($"工作表名: {worksheet.Name}");
Console.WriteLine($"数据范围: {worksheet.RangeUsed()?.RangeAddress}");
Console.WriteLine();
// 读取每个单元格
var rows = worksheet.RangeUsed().RowsUsed();
foreach (var row in rows)
{
// row.Cell(1) 是第 1 列,row.Cell(2) 是第 2 列...
string name = row.Cell(1).GetString();
string age = row.Cell(2).GetString();
string score = row.Cell(3).GetString();
Console.WriteLine($"姓名:{name,-6} 年龄:{age,-4} 分数:{score}");
}
}
}
}
4.4 跳过标题行 + 解析数据类型
using System;
using System.Collections.Generic;
using ClosedXML.Excel;
public class Student
{
public string Name;
public int Age;
public int Score;
}
class Program
{
static void Main()
{
string path = "students.xlsx";
List<Student> students = new List<Student>();
using (var workbook = new XLWorkbook(path))
{
var worksheet = workbook.Worksheet(1);
var rows = worksheet.RangeUsed().RowsUsed();
bool isFirstRow = true;
foreach (var row in rows)
{
if (isFirstRow)
{
isFirstRow = false;
continue; // 跳过标题行
}
students.Add(new Student
{
Name = row.Cell(1).GetString(),
Age = row.Cell(2).GetValue<int>(), // 直接获取数值类型
Score = row.Cell(3).GetValue<int>()
});
}
}
Console.WriteLine($"读取到 {students.Count} 名学生的数据:\n");
foreach (var s in students)
Console.WriteLine($" {s.Name,-6} | {s.Age}岁 | {s.Score}分");
Console.WriteLine($"\n 平均分: {students.Average(s => s.Score):F1}");
Console.WriteLine($" 最高分: {students.Max(s => s.Score)}");
}
}
4.5 读取指定行范围
// 读取 A2 到 C10 这个范围
var range = worksheet.Range("A2:C10");
// 或按行列索引读取
var cell = worksheet.Cell(3, 2); // 第 3 行,第 2 列(B3)
Console.WriteLine($"B3 单元格的值: {cell.GetString()}");
// 遍历指定行范围
for (int row = 2; row <= 6; row++) // 从第 2 行到第 6 行
{
string name = worksheet.Cell(row, 1).GetString();
if (string.IsNullOrEmpty(name)) break; // 遇到空行就停
int score = worksheet.Cell(row, 3).GetValue<int>();
Console.WriteLine($"{name}: {score}分");
}
4.6 写入 Excel
using ClosedXML.Excel;
// 创建工作簿
using (var workbook = new XLWorkbook())
{
var worksheet = workbook.Worksheets.Add("学生成绩");
// 写标题
worksheet.Cell(1, 1).Value = "姓名";
worksheet.Cell(1, 2).Value = "年龄";
worksheet.Cell(1, 3).Value = "分数";
// 写数据
worksheet.Cell(2, 1).Value = "张三";
worksheet.Cell(2, 2).Value = 18;
worksheet.Cell(2, 3).Value = 92;
worksheet.Cell(3, 1).Value = "李四";
worksheet.Cell(3, 2).Value = 19;
worksheet.Cell(3, 3).Value = 85;
// 保存
workbook.SaveAs("output.xlsx");
Console.WriteLine("Excel 文件已生成: output.xlsx");
}
五、读取 PDF 文件
5.1 需要安装的包
PDF 不是 C# 自带的,需要使用第三方库。推荐 UglyToad.PdfPig(免费、纯 C#):
Install-Package PdfPig
另一个选择是 iTextSharp(功能更强但商业用需付费),PdfPig 是 MIT 开源协议完全免费。
5.2 读取 PDF 中的文本
using System;
using UglyToad.PdfPig;
class Program
{
static void Main()
{
string path = "document.pdf";
using (var pdf = PdfDocument.Open(path))
{
Console.WriteLine($"PDF 信息:");
Console.WriteLine($" 总页数: {pdf.NumberOfPages}");
Console.WriteLine();
// 遍历每一页
foreach (var page in pdf.GetPages())
{
Console.WriteLine($"===== 第 {page.Number} 页 =====");
// 获取这一页的全部文本
string pageText = page.Text;
Console.WriteLine(pageText);
// 获取这一页的单词列表
var words = page.GetWords();
Console.WriteLine($"\n 本页共 {words.Count()} 个单词");
}
}
}
}
5.3 逐字读取 + 获取位置信息
using UglyToad.PdfPig;
using (var pdf = PdfDocument.Open("document.pdf"))
{
foreach (var page in pdf.GetPages())
{
Console.WriteLine($"===== 第 {page.Number} 页 =====");
// 获取每个单词(带位置信息)
foreach (var word in page.GetWords())
{
Console.WriteLine($" \"{word.Text}\" "
+ $"位置: ({word.BoundingBox.Left:F0}, {word.BoundingBox.Top:F0}) "
+ $"字体: {word.FontName} 字号: {word.FontSize}");
}
}
}
5.4 更简单的方式——提取纯文本
using UglyToad.PdfPig;
using System.Text;
static string ExtractAllText(string pdfPath)
{
StringBuilder sb = new StringBuilder();
using (var pdf = PdfDocument.Open(pdfPath))
{
foreach (var page in pdf.GetPages())
{
sb.AppendLine(page.Text);
}
}
return sb.ToString();
}
// 使用
string allText = ExtractAllText("document.pdf");
Console.WriteLine(allText);
六、文件操作的综合对比
6.1 各类型文件的读取方式
| 文件类型 | 推荐库/类 | 安装方式 | 难度 |
|---|---|---|---|
| TXT/CSV | File / StreamReader |
C# 自带 | 简单 |
| 二进制 | FileStream / BinaryReader |
C# 自带 | 中等 |
| Excel (.xlsx) | ClosedXML |
NuGet 安装 | 中等 |
PdfPig |
NuGet 安装 | 中等偏难 | |
| JSON | System.Text.Json |
C# 自带 | 简单 |
6.2 读取方式的通用模式
// ✅ 通用读取模式:
// 1. 检查文件是否存在
if (!File.Exists(path))
{
Console.WriteLine("文件不存在");
return;
}
// 2. 使用 using 语句(自动关闭文件)
using (var resource = new SomeReader(path))
{
// 3. 读取数据
var data = resource.Read();
// 4. 处理数据
Process(data);
} // ← 离开 using 时自动释放资源
6.3 异常处理——文件操作的必备
try
{
string content = File.ReadAllText("data.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException)
{
Console.WriteLine("错误:文件没找到,请检查文件路径");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("错误:没有权限读取该文件");
}
catch (IOException ex)
{
Console.WriteLine($"错误:读取文件时发生异常: {ex.Message}");
}
七、完整实战——多格式成绩汇总系统
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ClosedXML.Excel;
class Student
{
public string Name;
public int Score;
public string Source; // 数据来源
public override string ToString() => $"{Name,-6} | {Score}分 | 来源:{Source}";
}
class Program
{
static void Main()
{
Console.WriteLine("===== 多格式成绩汇总系统 =====\n");
List<Student> allStudents = new List<Student>();
// ===== 1. 读取 TXT 文件 =====
string txtPath = "scores.txt";
if (File.Exists(txtPath))
{
Console.WriteLine($"📝 读取 TXT: {txtPath}");
var txtStudents = File.ReadAllLines(txtPath)
.Where(line => !string.IsNullOrWhiteSpace(line))
.Select(line => line.Split(','))
.Where(parts => parts.Length >= 2)
.Select(parts => new Student
{
Name = parts[0].Trim(),
Score = int.Parse(parts[1].Trim()),
Source = "TXT"
});
allStudents.AddRange(txtStudents);
Console.WriteLine($" 读取到 {txtStudents.Count()} 条\n");
}
else
{
Console.WriteLine($"⚠️ 文件不存在: {txtPath}\n");
}
// ===== 2. 读取 Excel 文件 =====
string excelPath = "scores.xlsx";
if (File.Exists(excelPath))
{
Console.WriteLine($"📊 读取 Excel: {excelPath}");
try
{
using (var workbook = new XLWorkbook(excelPath))
{
var worksheet = workbook.Worksheet(1);
var rows = worksheet.RangeUsed().RowsUsed().Skip(1); // 跳过标题
foreach (var row in rows)
{
string name = row.Cell(1).GetString().Trim();
if (string.IsNullOrEmpty(name)) break;
allStudents.Add(new Student
{
Name = name,
Score = row.Cell(2).GetValue<int>(),
Source = "Excel"
});
}
}
Console.WriteLine($" 读取完毕\n");
}
catch (Exception ex)
{
Console.WriteLine($" Excel 读取失败: {ex.Message}\n");
}
}
else
{
Console.WriteLine($"⚠️ 文件不存在: {excelPath}\n");
}
// ===== 3. 汇总分析 =====
if (allStudents.Count == 0)
{
Console.WriteLine("没有读取到任何数据!");
return;
}
Console.WriteLine("===== 汇总结果 =====");
Console.WriteLine($"总人数: {allStudents.Count}");
foreach (var s in allStudents)
Console.WriteLine($" {s}");
Console.WriteLine($"\n平均分: {allStudents.Average(s => s.Score):F1}");
Console.WriteLine($"最高分: {allStudents.Max(s => s.Score)}");
Console.WriteLine($"最低分: {allStudents.Min(s => s.Score)}");
Console.WriteLine($"及格率: {(double)allStudents.Count(s => s.Score >= 60) / allStudents.Count * 100:F1}%");
}
}
八、常见易错点(避坑指南)
坑1:忘记检查文件是否存在
// ❌ 直接读,文件不存在就崩溃
string text = File.ReadAllText("不存在的文件.txt");
// ✅ 先检查
if (File.Exists("文件.txt"))
{
string text = File.ReadAllText("文件.txt");
}
else
{
Console.WriteLine("文件不存在");
}
坑2:没有关闭文件流
// ❌ 忘记关闭
FileStream fs = new FileStream("data.txt", FileMode.Open);
// ... 操作用完没关闭,文件被锁住
// ✅ 用 using 自动关闭
using (FileStream fs = new FileStream("data.txt", FileMode.Open))
{
// ... 操作
} // 自动调用 fs.Dispose() 关闭文件
坑3:二进制文件读写顺序不一致
// 写入
writer.Write(42);
writer.Write("hello");
writer.Write(3.14);
// ❌ 读的时候顺序错了
// int x = reader.ReadInt32();
// double d = reader.ReadDouble(); // ← 出错!实际读到的是 "hello" 开头
// string s = reader.ReadString();
// ✅ 读的顺序必须和写完全一致
int x = reader.ReadInt32();
string s = reader.ReadString();
double d = reader.ReadDouble();
坑4:一次性读大文件导致内存溢出
// ❌ 大文件(如 500MB 的日志)一次性读入内存
string all = File.ReadAllText("huge_log.txt"); // 可能 OutOfMemoryException
// ✅ 用 StreamReader 逐行处理
using (StreamReader reader = new StreamReader("huge_log.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
ProcessLine(line); // 逐行处理,只占一行内存
}
}
坑5:Excel 读取时没有 disposed
// ❌ 没有释放 Excel 资源
var workbook = new XLWorkbook("data.xlsx");
var sheet = workbook.Worksheet(1);
// ... 用完后没释放
// ✅ 用 using 或手动 workbook.Dispose()
using (var workbook = new XLWorkbook("data.xlsx"))
{
var sheet = workbook.Worksheet(1);
// ...
}
坑6:路径中的反斜杠问题
// ❌ 单反斜杠在 C# 里是转义字符
// string path = "C:\data\file.txt"; // 编译错误!
// ✅ 三种正确写法
string path1 = "C:\\data\\file.txt"; // 双反斜杠
string path2 = @"C:\data\file.txt"; // @ 前缀(逐字字符串)
string path3 = "C:/data/file.txt"; // 正斜杠也行
坑7:编码问题——中文乱码
// ❌ 文件是 UTF-8 编码,但用默认编码读可能乱码
string text = File.ReadAllText("chinese.txt"); // 中文变乱码!
// ✅ 指定编码
string text = File.ReadAllText("chinese.txt", System.Text.Encoding.UTF8);
// StreamReader 也一样
using (var reader = new StreamReader("chinese.txt", Encoding.UTF8))
{
string content = reader.ReadToEnd();
}
九、总结
文件读取速查表
| 文件类型 | 读方式 | 写方式 |
|---|---|---|
| TXT 小文件 | File.ReadAllText(path) |
File.WriteAllText(path, text) |
| TXT 大文件 | new StreamReader(path) 逐行 |
new StreamWriter(path) 逐行 |
| 二进制固定格式 | new BinaryReader(fs) |
new BinaryWriter(fs) |
| 二进制底层 | new FileStream(path, FileMode.Open) |
new FileStream(path, FileMode.Create) |
| Excel (.xlsx) | new XLWorkbook(path) + Worksheet() |
new XLWorkbook() + SaveAs() |
PdfDocument.Open(path) + GetPages() |
(需其他库) |
记忆口诀
文件操作三步走:检查存在、using 包裹、异常处理
文本小用 ReadAll,文本大用 StreamReader
二进制用 Binary,顺序读写要对齐
Excel 要装 ClosedXML,工作簿里找工作表
PDF 提取纯文本,PdfPig 免费又方便
读完记得关,using 帮你管
路径反斜杠,要么写俩要么加 @
一句话总结:C# 的文件操作都围绕
System.IO展开——TXT 小文件用File.ReadAllText,大文件用StreamReader逐行读;二进制用FileStream+BinaryReader(读写顺序必须一致);Excel 需要第三方库(推荐 ClosedXML);PDF 需要第三方库(推荐 PdfPig)。所有文件操作都用using包起来,读之前检查File.Exists。