1. File类:简单又高效
System.IO.File类就是“一个操作一个方法”的理念。它提供了一堆高级方法,把和操作系统打交道的复杂逻辑都封装好了,让你专注于自己的任务。
File类到底能干啥?
- 把文本或任意二进制数据写进文件。
- 读取文件内容,可以一次性读完,也可以按行读(针对文本文件)。
- 复制、删除和重命名已有文件。
- 检查文件是否存在,只要给定路径就行。
- 操作文件属性(比如创建日期、大小——通过相关类)。
为啥这么方便?
所有File的方法都是静态的。也就是说,你不用new对象,直接调用方法就行,比如:
File.WriteAllText("data.txt", "你好,文件!");
这种风格很像用Console类:简单明了,不用“new”,不用管流啥的。
如果你想要一个通用又顺手的文件操作工具,System.IO.File就是那个“瑞士军刀”。它有一堆方法,能读、写、复制、删除文件,基本操作全都能搞定。
怎么用File?
File类的所有方法都是静态的,所以不用new对象,直接写File.啥啥()就行。这和Console.WriteLine()很像,只不过这次是操作文件。
string text = "你好,世界!";
string path = "hello.txt";
// 把文本写进文件(如果文件已存在会覆盖!)
File.WriteAllText(path, text);
// 一次性读出整个文件内容
string readText = File.ReadAllText(path);
Console.WriteLine("文件内容: " + readText);
小技巧:如果你老是用ReadAllText和WriteAllText,十次八次都这样,估计你用错方法了——这些方法适合简单场景和小文件。
File类常用方法
| 方法 | 说明 |
|---|---|
|
检查指定路径下的文件是否存在 |
|
删除文件 |
|
复制文件 |
|
移动(或重命名)文件 |
|
把整个文件读成一个字符串 |
|
把字符串写进文件(如果已存在会覆盖) |
|
把文件的每一行读成字符串数组 |
|
把字符串数组写进文件(每个元素一行) |
|
把文件所有字节读成字节数组 |
|
把字节数组写进文件 |
2. 读写文本文件
下面咱们用实际例子看看File类最常用的方法。用这些方法前,记得在代码文件开头加上using System.IO;。
using System;
using System.IO; // 用File类必须加
using System.Linq; // 有时ReadLines会用到
一次性读整个文件
这是最常见的需求之一:把文本数据保存到文件,或者再读回来。
写一行(或全部文本)进文件:File.WriteAllText() 这个方法会在指定路径创建新文件。如果同名文件已存在,内容会被新文本完全覆盖。
string myFilePath = "notes.txt";
string textContent = "今天是学习C#的好日子。\n别忘了做作业。";
// 把所有文本写进文件
File.WriteAllText(myFilePath, textContent);
Console.WriteLine($"文件 '{myFilePath}' 已成功创建/覆盖。");
一次性把文件读成一个字符串:File.ReadAllText() 这个方法会把整个文本文件内容读出来,返回一个大字符串。
string myFilePath = "notes.txt";
// 注意:读文件前一定要先检查文件是否存在,避免出错!
if (File.Exists(myFilePath))
{
string content = File.ReadAllText(myFilePath);
Console.WriteLine($"文件 '{myFilePath}' 的内容:\n{content}");
}
else
{
Console.WriteLine($"错误: 文件 '{myFilePath}' 没找到,无法读取。");
}
重要提示:File.WriteAllText()和File.ReadAllText()很适合小文件和中等大小文件(比如笔记、配置文件、短日志)。但如果文件很大(几十、几百兆甚至几个G),一次性把内容读进内存会占用大量内存,甚至可能导致OutOfMemoryException。这种情况要用更高效的方法(后面会讲)。
3. 按行读取文件
当你要处理很大的文本文件,比如巨大的CSV报表、长日志文件或文本数据库时,一次性把整个文件读进内存(也就是把整个文件从磁盘加载到电脑内存)就不划算,甚至根本不行。这时候就要用File.ReadLines()。
File.ReadLines(path)——这个方法不会一次性加载整个文件。它返回一个特殊的集合(IEnumerable<string>),让你按需一行一行地读。这叫“懒加载”或“流式读取”。
string logFilePath = "application.log";
string text = """
INFO: 应用已启动。
WARN: 在模块X发现问题。
ERROR: 网络严重错误。
INFO: 应用已停止。
""";
// 假设我们有一个多行日志文件
File.WriteAllText(logFilePath, text);
// 每次循环只读并处理一行
foreach (string line in File.ReadLines(logFilePath))
Console.WriteLine($"- {line}");
优点:文件是分段读取和处理的,处理大文件时内存占用很低。
File.ReadLines()和File.ReadAllLines()的区别:
- File.ReadAllLines(path):一次性把所有行都读进内存,返回string[]数组。适合你需要同时访问所有行(比如排序、查找)。
- File.ReadLines(path):返回IEnumerable<string>,可以一行一行遍历。只有你要下一行时才会去读,非常适合超大文件,不用全读进内存。
4. 写字符串数组进文件
有时候你有一堆字符串(比如某个集合里的),想把每个字符串作为一行写进文件。
File.WriteAllLines(path, contents)
这个方法接收一个字符串数组(或任何字符串集合),每个字符串写成文件的一行。和WriteAllText()一样,如果文件已存在会覆盖。
string membersFilePath = "participants.txt";
string[] names = { "伊万·彼得罗夫", "玛丽亚·西多罗娃", "谢尔盖·科兹洛夫", "安娜·库兹涅佐娃" };
// 数组里的每个字符串都会写成文件的一行
File.WriteAllLines(membersFilePath, names);
// 检查内容
Console.WriteLine($"'{membersFilePath}' 的内容:");
foreach (string line in File.ReadLines(membersFilePath))
Console.WriteLine($"- {line}");
5. 操作二进制文件
File类不仅能操作文本数据,还能方便地读写二进制数据,也就是原始字节。这在保存特殊格式的设置、图片、音频或任何非文本数据时很有用。
写字节数组:File.WriteAllBytes() 创建或覆盖文件,把整个字节数组写进去。
string binaryDataPath = "settings.bin";
// 比如图片、视频、任意数据
byte[] settingsData = { 0x01, 0x0A, 0x2C, 0xFF, 0x00, 0x3F }; // 一些“原始”设置
File.WriteAllBytes(binaryDataPath, settingsData);
Console.WriteLine($"二进制数据已写入文件 '{binaryDataPath}'。");
注意:如果你用记事本打开'settings.bin',会看到乱码,因为这些字节不是文本。
读字节数组:File.ReadAllBytes() 把整个二进制文件内容读成字节数组。
if (File.Exists(binaryDataPath))
{
byte[] readSettings = File.ReadAllBytes(binaryDataPath);
Console.WriteLine($"从文件 '{binaryDataPath}' 读取了 {readSettings.Length} 字节。");
Console.Write("读取的字节(十六进制显示):");
foreach (byte b in readSettings)
Console.Write($"{b:X2} "); // :X2把字节格式化成两位十六进制
Console.WriteLine();
}
else
{
Console.WriteLine($"错误: 二进制文件 '{binaryDataPath}' 没找到。");
}
这些方法很适合保存小块二进制数据,比如缓存小图片或序列化对象。
6. 管理磁盘上的文件
File类还提供了磁盘文件管理的方法。
检查文件是否存在:File.Exists(path) 如果指定路径下有文件,返回true,否则false。强烈建议在读、删或其他可能出错的操作前先用这个方法。
string myFileToCheck = "report.txt";
// 先创建一个文件做例子
File.WriteAllText(myFileToCheck, "测试报告。");
if (File.Exists(myFileToCheck))
{
Console.WriteLine($"文件 '{myFileToCheck}' 找到了。");
// 现在可以安全地读、删等操作
}
else
{
Console.WriteLine($"文件 '{myFileToCheck}' 没找到。");
}
删除文件:File.Delete(path) 会把文件从磁盘彻底删掉。注意,这个操作不可恢复!
string fileToDelete = "temp_file.txt";
// 先创建一个文件做例子
File.WriteAllText(fileToDelete, "这个文件会被删除。");
if (File.Exists(fileToDelete))
{
File.Delete(fileToDelete);
Console.WriteLine($"文件 '{fileToDelete}' 已成功删除。");
}
else
{
Console.WriteLine($"文件 '{fileToDelete}' 已不存在或之前就被删了。");
}
7. 文件操作中的异常
操作文件总是要和操作系统、外部资源打交道,所以很容易出错。
一定要处理异常(错误)! 文件操作时可能遇到各种问题,你的程序要能优雅地处理,而不是直接崩溃。常见情况有:
- FileNotFoundException:你要访问的文件在指定路径下不存在。
- DirectoryNotFoundException:你要创建或查找文件的文件夹不存在。
- UnauthorizedAccessException:你的程序没有权限读、写或删文件/文件夹(比如系统文件或受保护区域)。
- IOException:通用的输入/输出错误,常见于文件被别的程序占用或磁盘空间不足。
操作文件时一定要用try-catch结构。这样可以“抓住”错误,做出相应处理(比如提示用户、写日志)。
string sensitiveFilePath = "secret_data.txt";
try
{
// 试着读一个可能不存在或没权限的文件
string secretContent = File.ReadAllText(sensitiveFilePath);
Console.WriteLine($"读取到的机密内容: {secretContent}");
}
catch (FileNotFoundException) // 具体错误:文件没找到
{
Console.WriteLine($"错误: 文件 '{sensitiveFilePath}' 没找到。请确认路径正确。");
}
catch (IOException ex) // 通用输入/输出错误(比如文件被占用)
{
Console.WriteLine($"操作文件 '{sensitiveFilePath}' 时发生IO错误: {ex.Message}");
}
catch (Exception ex) // 其他未知错误
{
Console.WriteLine($"发生了意外错误: {ex.Message}");
}
8. 什么时候System.IO.File不是最佳选择?
虽然File类非常万能,但有些场景用它并不合适,甚至根本不行:
超大文件(几十G以上):ReadAllText()/ReadAllBytes()会把整个文件读进内存。极大文件会导致OutOfMemoryException。这种情况要用File.ReadLines()(文本)或直接用流(Streams)(FileStream)分段读写。(流的内容下节课会讲。)
需要精细控制文件访问:如果你需要很细致地控制文件怎么打开(比如独占访问,别人不能读写,或者允许多个进程同时读),File类做不到。要用更底层的流类。
异步操作:高性能应用(比如Web服务器)需要文件操作不阻塞主线程,要用异步方法(比如ReadAllTextAsync()),这些也是基于流的。
部分读写:如果你只想从文件某个偏移读一段,或者往已有文件中间写数据,File类不行。得直接用文件流。
9. 总结
为啥System.IO.File这么受欢迎?
- 简单又简洁:静态方法让你一两行代码就能搞定复杂操作,开发效率高,代码也好读。
- 自动管理资源:File每次操作都会自动打开和关闭文件,你不用手动管理资源(手动容易出错)。
- 日常任务全能:大多数日常文本和二进制文件操作,File都能轻松搞定。
- 很常见:实际开发和面试都希望你会用File类的方法。
- 复杂性都封装好了:所有方法都把和操作系统打交道的复杂逻辑藏起来,给你一个清爽易懂的接口。
常见问题和补充说明
File.ReadAllText()能读多大的文件?
几十兆(< 100 MB)的文件一般没问题。再大(几百兆、几个G),用File.ReadAllText()可能会内存爆掉(OutOfMemoryException)。这种情况建议用File.ReadLines()按行读,或者用更高级的流式处理方法,后面会讲。
能不能在不存在的文件夹里创建/写文件?
不能。File.WriteAllText()、File.WriteAllBytes()等方法不会自动创建父文件夹。比如你写C:\MyAppData\Logs\current.log,但MyAppData或Logs文件夹不存在,就会抛出DirectoryNotFoundException。你得先用System.IO.Directory类的方法(比如Directory.CreateDirectory("myfolder"))确保文件夹都存在。
怎么查文件的创建日期、修改日期或大小?
要获取文件的详细信息,比如创建时间、最后修改时间、大小、属性(隐藏、只读等),用System.IO.FileInfo类。它有很多有用的属性。
GO TO FULL VERSION