1. 入门
当你用FileStream或者其他流类操作文件时,提前搞清楚打开文件会发生啥特别重要。经常会遇到文件已经存在的情况,这时你可能想往文件后面加新内容但不丢旧的,或者直接把文件整个覆盖掉。还有的时候,文件还没创建,你只想读它,但又不想误操作生成一个空文件。
比如,你写一个日记本,目标就是把新内容追加到已有文件末尾,保留之前写的所有东西。这就得用追加模式打开文件。再比如,你有个日志文件,每次程序启动都要重新生成,避免旧数据干扰。这时就要用能清空或新建文件的模式。如果你只是想读文件,肯定不希望没文件时自动新建一个空的,不然本来没文件的地方就多了个空文件。
这些不同的场景——追加、覆盖、只读不创建——在C#里都是通过System.IO.FileMode枚举的各种模式来实现的。每种模式都控制着打开或创建文件时的行为,让你能精确掌控文件操作时会发生什么。
2. FileMode枚举——通俗讲解
下面是FileMode枚举的主要取值:
| 取值 | 说明 | 文件已存在时 | 文件不存在时 |
|---|---|---|---|
|
新建文件。如果文件已经存在——抛出异常 | 报错 | OK,文件被创建 |
|
如果没有就新建,有的话覆盖原文件 | 覆盖 | OK,新建 |
|
打开已存在的文件。没有文件时——异常 | OK,打开 | 报错 |
|
有就打开,没有就新建 | OK,打开 | OK,新建 |
|
打开已存在文件并清空内容(长度变0) | OK,清空文件 | 报错 |
|
打开文件追加内容到末尾,没有就新建 | OK,追加到末尾 | OK,新建 |
图解:各种场景下的行为
flowchart TD
Start[开始]
A{文件存在吗?}
B1[打开追加]
B2[新建文件]
B3[覆盖文件]
B4[打开文件]
B5[错误:找不到文件]
B6[错误:文件已存在]
Start --> A
A --Append, OpenOrCreate--> B1
A --Create, Truncate--> B3
A --Open, Truncate--> B4
A --CreateNew--> B6
A --Create, OpenOrCreate, Append--> B2
A --Open, Truncate--> B5
3. 不同模式下的文件打开例子
来点实操,假设我们有个文本文件,往里写问候语和日期。我们看三个常见场景:
- A. 新建文件(CreateNew)
- B. 覆盖文件(Create)
- C. 追加内容到文件末尾(Append)
新建文件(FileMode.CreateNew)
如果文件已经存在,这个模式保证不会重新创建:程序会抛出IOException异常。适合不能丢数据或不想误删别人文件的场景。
using System;
using System.IO;
string filePath = "greeting.txt";
// 尝试新建文件——如果已存在会抛异常!
try
{
var stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine("你好!这是第一条记录。");
writer.WriteLine($"日期:{DateTime.Now}");
Console.WriteLine("文件创建成功。");
writer.Close();
}
catch (IOException)
{
Console.WriteLine($"文件 '{filePath}' 已经存在!不覆盖它。");
}
注意:如果你第二次运行这段代码——会进入catch,数据不会被覆盖。
覆盖文件(FileMode.Create)
这个模式很直接:有文件就清空,没有就新建。适合每次都要“从头开始”的场景,比如重保存报告。
using System;
using System.IO;
string filePath = "greeting.txt";
// 新建或完全覆盖文件
var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine("你好!我把文件完全更新了 :)");
writer.WriteLine($"已更新:{DateTime.Now}");
Console.WriteLine("文件已创建或覆盖。");
writer.Close();
多运行几次这段代码——文件里永远只有最新的内容,旧的全没了。
追加内容到文件末尾(FileMode.Append)
这个模式超适合日志、事件记录、访问统计等场景。没有文件就新建,有的话内容加到末尾。
using System;
using System.IO;
string filePath = "greeting.txt";
// 在文件末尾追加新内容
var stream = new FileStream(filePath, FileMode.Append, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine($"又一条问候!日期:{DateTime.Now}");
Console.WriteLine("新内容已追加到文件末尾。");
writer.Close();
多次运行这段代码,文件会越来越大,每次新内容都在最后。
4. 怎么选文件打开模式?实用建议
真实场景举例:
- 日志:几乎总是用Append。日志被覆盖管理员会心痛,坏人会偷笑。
- 导出报告:一般用Create,避免文件夹里堆一堆旧版本。
- 关键数据导出:用CreateNew。有文件就报错,啥都不动。
- 读文件:根本不用创建模式,直接用FileMode.Open——没文件就说明有问题。
写C#代码时,经常用File类的静态方法,它们内部会自动选合适的模式。比如File.AppendAllText总是用Append,File.WriteAllText用Create。
5. 文件模式和常见坑
有点小陷阱要注意:
用FileMode.Create或FileMode.Truncate打开文件时,内容会被清空。小心点——数据丢失不仅新手会遇到,老程序员也踩过坑。
用FileMode.Open或FileMode.Truncate打开不存在的文件,会抛FileNotFoundException。记得用File.Exists(path)先检查文件在不在。
Append模式只能写,不能读。如果你要读写都能用,考虑用其他模式(OpenOrCreate加合适的FileAccess)。
例子:读写文件
有时候你既想读又想写(比如开个银行账户——要存钱也要查余额)。用FileMode.OpenOrCreate:
using System.IO;
string filePath = "balance.txt";
// 读写打开文件,没有就新建
var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
// 这里可以读也可以写
6. 实用细节
文件打开模式流程
graph TD
A[请求文件] --> B[选择模式]
B --> C1[CreateNew] -->|如果文件存在| D1[错误]
C1 -->|文件不存在| E1[新建]
B --> C2[Create] -->|总是| F1[新建/覆盖]
B --> C3[Open] -->|文件存在| G1[打开]
C3 -->|文件不存在| D1
B --> C4[OpenOrCreate] -->|文件存在| G1
C4 -->|文件不存在| E1
B --> C5[Append] -->|文件存在| H1[打开追加]
C5 -->|文件不存在| E1
B --> C6[Truncate] -->|文件存在| I1[清空]
C6 -->|文件不存在| D1
文件打开模式对比表
| FileMode | 文件存在 | 文件不存在 | 数据访问 | 会清空旧内容吗? |
|---|---|---|---|---|
|
异常 | 新建 | 写 | 不适用 |
|
覆盖 | 新建 | 写 | 是 |
|
打开 | 异常 | 读/写 | 否 |
|
打开 | 新建 | 读/写 | 否 |
|
清空 | 异常 | 写 | 是 |
|
打开追加 | 新建 | 只能写 | 否(追加) |
和File类高级方法的对比
- File.WriteAllText(path, text) → 内部用FileMode.Create,也就是会覆盖旧文件。
- File.AppendAllText(path, text) → 用Append,内容都加到末尾。
- File.ReadAllText(path) → 以只读模式打开(FileMode.Open)。
有时候这些方法就够了,但用FileStream加上明确的模式能让你完全掌控文件操作。
不同操作系统下的注意点
很多开发者没注意到,在Windows下,如果一个文件被别的进程以写方式打开,再次打开会报错。所以要合理用FileShare参数,控制文件的共享访问(后面会专门讲)。不同系统的锁和权限机制可能不一样,但文件打开模式的原理是通用的。
GO TO FULL VERSION