C# FileStream 使用详解
总目录
前言
在 C# 编程中,文件操作是常见的任务之一。FileStream
类是 System.IO
命名空间中的一个重要类,它提供了对文件的读取和写入操作的底层支持。本文将详细介绍 FileStream
的使用方法,包括如何创建、读取、写入文件,以及一些实用的技巧和注意事项。
一、什么是 FileStream ?
1. 定义
FileStream
类位于System.IO
命名空间中,它继承自Stream
类,是一个用于表示文件的字节流的类。。FileStream
允许我们以字节流的形式同步或异步读写文件,可以指定文件的访问模式、共享模式和缓冲区大小等参数。
FileStream
是许多高级文件操作类(如StreamReader
、StreamWriter
、BinaryReader
和BinaryWriter
)的基础。
2. FileStream 的核心功能
FileStream
是对文件的字节级操作类,允许直接访问文件的物理存储。其主要功能包括:
- 读写字节数据:通过
Read
、Write
方法操作文件的原始字节。 - 控制文件模式:通过
FileMode
枚举指定文件的打开、创建、覆盖等行为。 - 管理文件访问权限:通过
FileAccess
和FileShare
控制读写权限及与其他进程的共享方式。 - 定位文件指针:通过
Seek
方法快速跳转到文件的任意位置。
3. 适用场景
FileStream
作为C#文件操作的基础设施,其灵活性和高性能特性使其在以下场景不可替代:
- 二进制文件(如图片、音视频)处理(如图像加密)
- 超大文件分块读写
- 需要精准控制文件指针的随机访问
- 低延迟高吞吐量IO操作
二、基础用法与参数详解
1. 创建 FileStream 对象
在使用 FileStream
之前,需要先创建一个 FileStream
对象。这可以通过 FileStream
的构造函数来完成。FileStream
的构造函数有多个重载版本,常用的有以下几种:
1)基本构造函数
public FileStream(string path, FileMode mode);
path
:文件的路径。mode
:指定文件的打开模式,FileMode
是一个枚举类型,常见的值包括:
模式 | 文件存在时 | 文件不存在时 | 指针位置 |
---|---|---|---|
CreateNew | 抛出异常 | 创建新文件 | 开头 |
Create | 覆盖现有文件 | 创建新文件 | 开头 |
Open | 打开文件 | 抛出异常 | 开头 |
OpenOrCreate | 打开文件 | 创建新文件 | 开头 |
Truncate | 清空文件内容 | 抛出异常 | 开头 |
Append | 打开并追加 | 创建新文件 | 末尾 |
// 安全创建文件示例
if(File.Exists("temp.data"))
{
using var fs = new FileStream("temp.data", FileMode.Truncate);
}
else
{
using var fs = new FileStream("temp.data", FileMode.CreateNew);
}
2)带访问模式的构造函数
public FileStream(string path, FileMode mode, FileAccess access);
access
:指定文件的访问模式,FileAccess
是一个枚举类型,常见的值包括:Read
:只读访问。Write
:只写访问。ReadWrite
:读写访问。
// 创建一个 FileStream 对象,用于读取文件
FileStream fsRead = new FileStream("example.txt", FileMode.Open, FileAccess.Read);
// 创建一个 FileStream 对象,用于写入文件
FileStream fsWrite = new FileStream("example.txt", FileMode.Create, FileAccess.Write);
3)带共享模式的构造函数
public FileStream(string path, FileMode mode, FileAccess access, FileShare share);
share
:指定文件的共享模式,FileShare
是一个枚举类型,常见的值包括:None
:不允许其他进程或线程操作。Read
:允许其他进程或线程以只读模式打开。Write
:允许其他进程或线程以只写模式打开。ReadWrite
:允许其他进程或线程以读写模式打开。Delete
:允许其他进程或线程删除该文件。
4)参数说明
▶ 关键参数
参数 | 描述 |
---|---|
path | 文件路径,支持绝对路径或相对路径。 |
mode | 文件操作模式(如 FileMode.Create 创建新文件,FileMode.Open 打开现有文件)。 |
access | 访问权限(如 FileAccess.Read 、FileAccess.Write 、FileAccess.ReadWrite )。 |
share | 共享模式,控制其他进程或线程是否可同时访问文件(如 FileShare.Read 允许其他进程读取)。 |
▶ 高级构造参数
// 最完整参数形式
var fs = new FileStream(
path: "data.bin",
mode: FileMode.OpenOrCreate,
access: FileAccess.ReadWrite,
share: FileShare.Read,
bufferSize: 8192,
options: FileOptions.Asynchronous
);
参数名称 | 作用 | 典型值示例 |
---|---|---|
bufferSize | 设置缓冲区大小(字节) | 建议设为4096的倍数提升性能,如 4096, 8192, 16384 |
useAsync | 启用异步操作 | true/false |
FileOptions | 文件高级选项 | FileOptions.SequentialScan |
2. 向文件中写入数据
使用 FileStream
向文件中写入数据,可以通过 Write
方法实现。Write
方法的定义如下:
public override void Write(byte[] array, int offset, int count);
array
:要写入的字节数组。offset
:数组中的起始位置。count
:要写入的字节数。
1)写入文件
// 创建并写入文件
using (FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write))
{
byte[] data = { 1, 2, 3, 4 };
fs.Write(data, 0, data.Length); // 写入 4 字节
}
2)追加写入
// 追加模式写入
using (FileStream fs = new FileStream("log.txt", FileMode.Append, FileAccess.Write))
{
byte[] data = Encoding.UTF8.GetBytes("追加内容\n");
fs.Write(data, 0, data.Length);
}
3. 从文件中读取数据
使用 FileStream
从文件中读取数据,可以通过 Read
方法实现。Read
方法的定义如下:
public override int Read(byte[] array, int offset, int count);
array
:用于存储读取到的数据的字节数组。offset
:数组中的起始位置。count
:要读取的字节数。- 返回值:实际读取到的字节数。
// 打开并读取文件
using (FileStream fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[4];
// 读取到缓冲区
fs.Read(buffer, 0, buffer.Length); // 读取 4 字节
Console.WriteLine(BitConverter.ToString(buffer)); // 输出 "01-02-03-04"
}
4. 文件定位
在读取或写入文件时,可能需要在文件中进行定位操作。FileStream
提供了 Position
属性和 Seek
方法来实现文件定位。
1)Position 属性
Position
属性获取或设置当前读写位置。
// 记录当前位置
long pos = fs.Position;
// 设置当前读写位置为文件开头
fs.Position = 0;
// 回退100字节
fs.Position -= 100;
2)Seek 方法
Seek
方法用于在流中查找指定位置。其定义如下:
public override long Seek(long offset, SeekOrigin origin);
offset
:相对于origin
的偏移量。origin
:指定偏移量的参考点,SeekOrigin
是一个枚举类型,常见的值包括:Begin
:从流的开头开始计算。Current
:从当前位置开始计算。End
:从流的末尾开始计算。
- 返回值:新的位置。
通过 Seek
方法可以快速跳转到文件的任意位置:
// 定位到文件末尾
long position = fs.Seek(0, SeekOrigin.End); // 0 表示偏移量,SeekOrigin.End 表示从末尾开始
// 定位到文件开头的第 10 个字节
fs.Seek(10, SeekOrigin.Begin);
示例:文件定位
using (FileStream fs = new FileStream("example.txt", FileMode.Open))
{
// 定位到文件第5个字节的地方
fs.Seek(5, SeekOrigin.Begin);
// 创建字节数组,用于存储读取到的数据
byte[] data = new byte[fs.Length - fs.Position];
// 读取数据
int bytesRead = fs.Read(data, 0, data.Length);
// 输出读取到的数据
Console.WriteLine("从文件中间读取到的数据:");
Console.WriteLine(System.Text.Encoding.UTF8.GetString(data, 0, bytesRead));
}
5. 文件的扩展和清空
1)设置文件长度
使用 SetLength
方法可以设置文件的长度。如果新长度大于当前长度,则文件将被扩展;如果新长度小于当前长度,则文件将被截断。
// 将文件长度设置为 100 字节
fs.SetLength(100);
2)清空文件
要清空文件内容,可以将文件长度设置为 0。
// 清空文件
fs.SetLength(0);
三、FileStream 的高级使用
1. 异步操作
FileStream
支持异步读取和写入操作,这在处理大文件或需要提高程序响应性时非常有用。
1)异步写入
使用 BeginWrite
和 EndWrite
方法可以实现异步写入。
class Program
{
static void Main()
{
// 文件路径
string path = "example.txt";
// 要写入的数据
byte[] data = new byte[] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 };
using (FileStream fs = new FileStream(path, FileMode.Create))
{
// 异步写入数据
IAsyncResult result = fs.BeginWrite(data, 0, data.Length, null, null);
result.AsyncWaitHandle.WaitOne();
// 确保写入完成
fs.EndWrite(result);
}
Console.WriteLine("数据已异步写入文件!");
}
}
2)异步读取
使用 BeginRead
和 EndRead
方法可以实现异步读取。
class Program
{
static void Main()
{
// 文件路径
string path = "example.txt";
using (FileStream fs = new FileStream(path, FileMode.Open))
{
// 创建字节数组,用于存储读取到的数据
byte[] data = new byte[fs.Length];
// 异步读取数据
IAsyncResult result = fs.BeginRead(data, 0, data.Length, null, null);
result.AsyncWaitHandle.WaitOne();
// 确保读取完成
int bytesRead = fs.EndRead(result);
// 输出读取到的数据
Console.WriteLine("异步读取到的数据:");
Console.WriteLine(System.Text.Encoding.UTF8.GetString(data, 0, bytesRead));
}
}
}
3)异步操作最佳实践
async Task ProcessLargeFile()
{
byte[] buffer = new byte[4096];
using var fs = new FileStream("bigfile.dat", FileMode.Open,
FileAccess.Read, FileShare.Read,
4096, true);
while(true)
{
int read = await fs.ReadAsync(buffer, 0, buffer.Length);
if(read == 0) break;
// 处理数据
}
}
// 异步读取
public async Task ReadAsync()
{
using (FileStream fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[4];
await fs.ReadAsync(buffer, 0, buffer.Length);
}
}
2. 性能优化
性能优化策略:
- 异步IO优先:充分利用现代存储设备的并行能力
- 合理缓冲区:根据访问模式调整bufferSize参数
- 顺序访问标记:设置FileOptions.SequentialScan
- 避免频繁开关:保持流打开处理批量操作
- 使用内存映射:处理超大文件时效率倍增
- 释放及时:及时Dispose释放系统资源
1)缓冲区优化
通过 SetLength
、Length
属性管理文件大小,或通过 BufferSize
构造参数调整缓冲区大小:
// 创建带自定义缓冲区的流(默认 4KB)
using (FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 8192))
{
// 缓冲区大小为 8KB
}
var fs = new FileStream("prealloc.dat", FileMode.Create);
fs.SetLength(1024 * 1024 * 1024); // 预分配1GB空间
2)大文件处理
▶ 分块读写
避免一次性加载大文件到内存:
using (FileStream fs = new FileStream("large_file.bin", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// 处理 buffer 的前 bytesRead 字节
}
}
▶ 内存映射文件(Memory-Mapped Files)
通过 MemoryMappedFile
处理超大文件(超高性能场景):
using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile("large_file.bin", FileMode.Open))
{
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
byte[] buffer = new byte[1024];
accessor.ReadArray(0, buffer, 0, buffer.Length); // 读取前 1KB
}
}
▶ 混合缓冲策略
// 禁用系统缓冲(适用于SSD)
var fs = new FileStream("ssd.bin", FileOptions.WriteThrough);
3. 与二进制类结合使用
- 通过
BinaryReader/BinaryWriter
简化二进制数据操作:// 写入二进制数据 using (FileStream fs = new FileStream("data.bin", FileMode.Create)) using (BinaryWriter writer = new BinaryWriter(fs)) { writer.Write(42); // 写入 int writer.Write(3.14f); // 写入 float }
4. 实际应用场景示例
示例1:大文件分块处理
避免一次性加载大文件导致内存溢出:
using (var fs = new FileStream("4GB_video.mp4", FileMode.Open, FileAccess.Read))
{
byte[] chunk = new byte[1024 * 1024]; // 1MB分块
while (fs.Position < fs.Length)
{
int read = fs.Read(chunk, 0, chunk.Length);
// 处理当前分块(如加密/压缩)
}
}
示例2:二进制文件解析
解析PNG文件头示例:
using (var fs = new FileStream("image.png", FileMode.Open))
{
byte[] header = new byte[8];
fs.Read(header, 0, 8);
if (header[0] == 0x89 && header[1] == 0x50)
{
// 检测PNG魔数
Console.WriteLine("Valid PNG file");
}
}
示例3:异步高性能写入
利用异步模式提升吞吐量:
async Task WriteLogAsync(string message)
{
using (var fs = new FileStream("app.log", FileMode.Append, FileAccess.Write, FileShare.None, 4096, FileOptions.Asynchronous))
{
byte[] data = Encoding.UTF8.GetBytes(message + "\n");
await fs.WriteAsync(data, 0, data.Length);
}
}
示例4:断点续传实现
class FileResume
{
private long _currentPosition;
public void ResumeDownload(string url, string localPath)
{
using var fs = new FileStream(localPath, FileMode.OpenOrCreate,
FileAccess.Write);
_currentPosition = fs.Length;
fs.Seek(_currentPosition, SeekOrigin.Begin);
// 从指定位置下载网络文件
DownloadFromPosition(url, _currentPosition);
}
}
四、常见问题与注意事项
1. 常见问题
1)文件被占用导致异常
问题:尝试打开文件时抛出 IOException
,提示文件被其他进程占用。
解决:
- 使用
FileShare
参数允许共享访问:new FileStream("file.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- 确保其他进程已释放文件资源。
2)权限不足
问题:无权读写指定路径的文件。
解决:
- 检查文件路径的访问权限(如 NTFS 权限)。
- 以管理员身份运行程序(Windows)。
3)文件指针越界
问题:尝试读取或写入超出文件长度的位置。
解决:
- 使用
Seek
方法前检查文件长度:if (fs.Position + 10 > fs.Length) { throw new ArgumentOutOfRangeException("超出文件范围"); }
2. 注意事项
1)使用 using 语句
FileStream
实现了 IDisposable
接口,因此建议使用 using
语句来确保资源被正确释放。
using (FileStream fs = new FileStream(path, FileMode.Open))
{
// 操作文件流
}
2)处理异常
在文件操作中,可能会遇到各种异常情况,如文件不存在、权限不足等。因此,在使用 FileStream
时,应妥善处理异常。
try
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
// 操作文件流
}
}
catch (FileNotFoundException)
{
Console.WriteLine("文件未找到!");
}
catch (IOException)
{
Console.WriteLine("发生 I/O 错误!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("访问被拒绝!");
}
3)编码问题
在处理文本文件时,需要注意编码问题。可以使用 System.Text.Encoding
类来指定编码。
// 以 UTF-8 编码读取文件
byte[] data = File.ReadAllBytes(path);
string text = System.Text.Encoding.UTF8.GetString(data);
4)性能优化
在处理大文件时,可以考虑使用缓冲区来提高性能。大文件处理时建议设置更大的bufferSize
using System;
using System.IO;
class Program
{
static void Main()
{
// 文件路径
string path = "largefile.txt";
// 缓冲区大小
int bufferSize = 4096;
// 创建 FileStream 对象,以读取模式打开文件
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize))
{
// 创建缓冲区
byte[] buffer = new byte[bufferSize];
int bytesRead;
// 循环读取数据
while ((bytesRead = fs.Read(buffer, 0, bufferSize)) > 0)
{
// 处理缓冲区中的数据
string text = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.Write(text);
}
}
}
}
5)文件锁定与并发
- 使用
FileShare.ReadWrite
允许多进程共享文件 - 通过
Lock()
方法锁定文件区域防止冲突:fs.Lock(0, fs.Length); // 全文件锁定 // 执行关键操作 fs.Unlock(0, fs.Length);
五、最佳实践总结
1. 资源管理
- 使用
using
语句:确保流在操作后自动关闭,避免文件被锁定。 - 避免长生命周期流:不要在长时间运行的代码中保持
FileStream
打开。
2. 异步优先
- 对于 I/O 密集型操作,优先使用异步方法(如
ReadAsync
、WriteAsync
),提升程序响应性。
3. 缓冲区调优
- 根据场景调整缓冲区大小:
- 小文件:默认 4KB 即可。
- 大文件:增大缓冲区(如 8KB-64KB)提升性能。
缓冲区配置原则
文件类型 | 推荐缓冲区大小 | 理由 |
---|---|---|
小型文件 | 默认4KB | 避免内存浪费 |
大文件顺序读 | 64KB-1MB | 减少IO次数 |
随机访问 | 4KB-16KB | 提高定位准确性 |
5. 异常处理
- 捕获
IOException
、UnauthorizedAccessException
等常见异常:try { // 文件操作 } catch (IOException ex) { Console.WriteLine("IO 错误:" + ex.Message); }
6. 推荐实践代码
// 完整示例:读写二进制文件
public static async Task ProcessFileAsync()
{
// 写入数据
using (FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write))
using (BinaryWriter writer = new BinaryWriter(fs))
{
writer.Write(42); // 写入整数
writer.Write("Hello");
}
// 读取数据
using (FileStream fs = new FileStream("data.bin", FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
int num = reader.ReadInt32(); // 42
string text = reader.ReadString(); // "Hello"
}
}
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。