在.NET的文件操作体系中,FileStream处于非常核心的位置。无论是文本文件的读写,还是二进制数据的传输,底层几乎都离不开它。StreamWriter、MemoryStream等类在处理文件时,最终也会把数据交给FileStream来完成与磁盘的交互。掌握FileStream的用法,等于掌握了.NET文件操作的地基。本文将围绕FileStream的创建、读写、复制、异步操作以及常见异常处理几个方面,结合可运行的代码示例展开讲解。

一、FileStream的基本概念与构造方式
FileStream继承自Stream类,表示指向文件的流,它把文件看作一连串的字节序列。你可以把文件想象成一条长长的管道,FileStream就像管道上的一个阀门,通过它可以从文件中取字节,也可以往文件中塞字节。与直接调用File类的静态方法相比,FileStream提供了更细粒度的控制,比如指定打开方式、访问权限、共享权限以及缓冲区大小。
创建一个FileStream实例最常见的方式是使用构造函数。构造函数有多个重载,最典型的形式如下:
using System.IO; // 以追加模式打开文件,如果文件不存在则创建 FileStream fs = new FileStream(@"C:\temp\demo.txt", FileMode.Append, FileAccess.Write); fs.Dispose();
这里出现了三个关键参数:FileMode、FileAccess和FileShare,理解它们是正确使用FileStream的前提。
FileMode决定了文件以什么方式打开,常用取值包括:
CreateNew:新建文件,如果文件已存在则抛出异常Create:创建文件,如果已存在则覆盖Open:打开已有文件,文件不存在则抛出异常OpenOrCreate:文件存在则打开,不存在则创建Truncate:打开文件并清空内容Append:追加模式,只能写入,指针自动移到文件末尾
FileAccess指定访问类型,取值为Read、Write或ReadWrite。FileShare控制其他线程或进程能否同时访问该文件,比如FileShare.Read允许其他进程读取但不允许写入,FileShare.None则独占文件。很多初学者遇到的“文件正由另一进程使用,无法访问”的异常,根源就在于FileShare设置不当或者前一个流没有释放。
除了直接new之外,File类的静态方法也能拿到FileStream,例如File.Open、File.Create、File.OpenRead、File.OpenWrite,这些方法本质上是构造函数的简化封装,代码更短,适合简单场景。
二、使用FileStream完成文件的写入操作
FileStream写入数据的核心方法是Write,它接收一个字节数组、偏移量和长度。也就是说,无论你写入的是字符串还是对象,最终都要先转成字节数组。字符串转字节数组需要借助Encoding类指定编码,这一点非常关键,因为编码选择不当会导致文件内容乱码。
下面是一个完整的写入示例:
using System;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
// 使用using确保流在使用完毕后自动释放
using (FileStream fs = new FileStream(@"C:\temp\test.txt", FileMode.Create, FileAccess.Write))
{
string content = "这是使用FileStream写入的内容";
byte[] buffer = Encoding.UTF8.GetBytes(content);
fs.Write(buffer, 0, buffer.Length);
}
Console.WriteLine("写入完成");
}
}这段代码有几个值得注意的地方。第一,使用了using语句块,它会在块结束时自动调用Dispose方法关闭文件句柄,这是释放文件资源的标准做法。第二,构造时缓冲区大小没有显式指定,默认是4096字节,如果你的单次写入量特别大,可以在构造函数中传入更大的缓冲区来减少磁盘IO次数。第三,写入完成后FileStream内部有缓冲机制,如果中途强制关闭程序可能导致数据没有真正落盘,正常Dispose时会自动Flush。
对于大文件写入,不要一次性把所有数据读进内存再写,而应该分块写入。比如生成一个较大的日志文件时,采用固定大小的缓冲区循环写入,内存占用会保持在较低水平,这也是流式处理思想的核心价值。
三、读取文件内容与字节数据处理
读取操作使用Read方法,参数同样是字节数组、偏移量和长度,返回值是实际读取到的字节数。这个返回值必须检查,因为当读到文件末尾时,返回值会小于请求的长度甚至为0,忽略这一点容易读出脏数据。
using System;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
using (FileStream fs = new FileStream(@"C:\temp\test.txt", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[fs.Length];
int bytesRead = fs.Read(buffer, 0, buffer.Length);
string text = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine(text);
}
}
}上面这种方式适合小文件,直接按文件长度分配缓冲区一次读完。但要注意fs.Length是long类型,如果文件极大,直接转成int会溢出。对于大文件,标准的写法是循环读取:
using (FileStream fs = File.OpenRead(@"C:\temp\bigfile.dat"))
{
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// 每次处理8192字节,内存占用恒定
ProcessChunk(buffer, bytesRead);
}
}这种分块模式在实际开发中极为常用,比如计算文件哈希、上传下载文件、音视频转码等场景,都是按块读取逐段处理。此外,FileStream还提供了Position和Seek成员用于移动读写指针,可以实现随机访问,例如从文件的第1000个字节处开始读取,这在解析固定格式文件时很有用。
四、文件复制与异步读写实践
掌握了读和写,文件复制就变成了边读边写的组合操作。虽然框架提供了File.Copy方法,但手写一遍复制逻辑对理解流的本质非常有帮助:
public static void CopyFile(string sourcePath, string destPath)
{
using (FileStream source = File.OpenRead(sourcePath))
using (FileStream dest = new FileStream(destPath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[1024 * 64]; // 64KB缓冲区
int bytesRead;
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
{
dest.Write(buffer, 0, bytesRead);
}
}
}缓冲区大小的选择有讲究:太小会导致IO次数过多,太大会浪费内存且提升有限。经验值在64KB到1MB之间,具体可以根据实际测试调整。实际上,FileStream本身提供了一个CopyTo方法,内部就是类似的循环逻辑,日常开发直接调用即可。
当文件操作发生在UI线程或者高并发服务中,同步读写会阻塞线程,这时候应该用异步方法。FileStream提供了ReadAsync和WriteAsync,配合async和await关键字使用非常自然:
public static async Task WriteAsync(string path, string content)
{
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true))
{
byte[] data = Encoding.UTF8.GetBytes(content);
await fs.WriteAsync(data, 0, data.Length);
}
}注意构造函数中的useAsync: true参数,它会让流采用异步IO而非线程池模拟,真正减少线程阻塞。在ASP.NET服务或桌面程序的UI线程中处理文件时,异步方式能明显改善响应能力。
五、常见问题与异常排查
使用FileStream时最经典的异常是IOException,提示“文件正由另一进程使用”。排查思路有三步:首先确认自己的代码是否所有流都正确释放,尤其是异常路径下也要保证Dispose执行,最稳妥的办法就是始终用using包裹;其次检查FileShare参数,如果打开时声明了独占访问,其他进程自然无法触碰;最后看看是不是杀毒软件、压缩工具等外部程序临时锁住了文件。
另一个容易踩的坑是编码问题。用FileStream写中文时如果用了ASCII编码或者读写两端编码不一致,就会出现乱码。建议统一使用UTF-8,读取文本时如果文件带BOM,Encoding.UTF8会自动识别处理。另外,若只是简单的文本读写,用StreamWriter和StreamReader包装FileStream会更省事,它们负责编码转换和按行读写,代码可读性更好。
最后提醒一点,路径相关的异常也不少见。路径中包含非法字符、目录不存在、权限不足都会抛出异常,写入前可以用Directory.Exists做检查,必要时先创建目录。总之,FileStream的使用要点可以归纳为:选对FileMode、及时释放资源、大文件分块处理、注意编码一致,掌握这四条,文件操作基本不会出大问题。
FileStreamC#文件流文件读写修改时间:2026-09-04 06:20:41