当前位置: 首页 > article >正文

C# FileStream 使用详解

总目录


前言

在 C# 编程中,文件操作是常见的任务之一。FileStream 类是 System.IO 命名空间中的一个重要类,它提供了对文件的读取和写入操作的底层支持。本文将详细介绍 FileStream 的使用方法,包括如何创建、读取、写入文件,以及一些实用的技巧和注意事项。


一、什么是 FileStream ?

1. 定义

FileStream类位于System.IO命名空间中,它继承自Stream类,是一个用于表示文件的字节流的类。。FileStream允许我们以字节流的形式同步或异步读写文件,可以指定文件的访问模式、共享模式和缓冲区大小等参数。

FileStream 是许多高级文件操作类(如 StreamReaderStreamWriterBinaryReaderBinaryWriter)的基础。

2. FileStream 的核心功能

FileStream 是对文件的字节级操作类,允许直接访问文件的物理存储。其主要功能包括:

  • 读写字节数据:通过 ReadWrite 方法操作文件的原始字节。
  • 控制文件模式:通过 FileMode 枚举指定文件的打开、创建、覆盖等行为。
  • 管理文件访问权限:通过 FileAccessFileShare 控制读写权限及与其他进程的共享方式。
  • 定位文件指针:通过 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.ReadFileAccess.WriteFileAccess.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)异步写入

使用 BeginWriteEndWrite 方法可以实现异步写入。

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)异步读取

使用 BeginReadEndRead 方法可以实现异步读取。

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. 性能优化

性能优化策略:

  1. 异步IO优先:充分利用现代存储设备的并行能力
  2. 合理缓冲区:根据访问模式调整bufferSize参数
  3. 顺序访问标记:设置FileOptions.SequentialScan
  4. 避免频繁开关:保持流打开处理批量操作
  5. 使用内存映射:处理超大文件时效率倍增
  6. 释放及时:及时Dispose释放系统资源

1)缓冲区优化

通过 SetLengthLength 属性管理文件大小,或通过 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 密集型操作,优先使用异步方法(如 ReadAsyncWriteAsync),提升程序响应性。

3. 缓冲区调优

  • 根据场景调整缓冲区大小:
    • 小文件:默认 4KB 即可。
    • 大文件:增大缓冲区(如 8KB-64KB)提升性能。

缓冲区配置原则

文件类型推荐缓冲区大小理由
小型文件默认4KB避免内存浪费
大文件顺序读64KB-1MB减少IO次数
随机访问4KB-16KB提高定位准确性

5. 异常处理

  • 捕获 IOExceptionUnauthorizedAccessException 等常见异常:
    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 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


http://www.kler.cn/a/613194.html

相关文章:

  • 如何降低频谱分析仪的底噪
  • 蓝桥杯[每日一题] 真题:连连看
  • Mathtype无法插入到Word中
  • 【大模型】激活函数之SwiGLU详解
  • Vue 项目中使用$refs来访问组件实例或 DOM 元素,有哪些注意事项?
  • 最接近的三数之和
  • CORDIC算法:三角函数的硬件加速革命——从数学原理到FPGA实现的超高效计算方案
  • 2.1-WAF\CDN\OSS\反向代理\负载均衡
  • 自动驾驶中基于潜在世界模型学习多概率决策(LatentDriver)
  • 《2核2G阿里云神操作!Ubuntu+Ollama低成本部署Deepseek模型实战》
  • 51c嵌入式~三极管~合集1
  • 剑指Offer49 -- DP_贪心
  • Meatachat:演示版(AI组件库、Chat、智能对话、移动适配)(附源码)
  • mybatis里in关键字拼接id问题
  • Jmeter-负载测试
  • 算法基础——模拟
  • 如何选择适合的实验室铸铁地板和铸铁试验平板?北重专业帮助指南
  • OpenHarmony子系统开发 - 显示管理
  • 软件工程面试题(七)
  • 爬虫的第三天——爬动态网页