CodeGym /课程 /C# SELF /类 FileStream

类 FileStream

C# SELF
第 35 级, 课程 3
可用

1. 类 FileStream:分块操作

你可以把FileStream想象成一根直接连到你硬盘文件的水管。通过这根管子你可以有节奏地传输数据:往文件里写字节(写入)或者从文件里读字节(读取)。和那些高级方法只给你“一杯水”不同,FileStream直接让你拧水龙头,想放多少水(字节)自己说了算。

FileStream是字节级别的。这意思就是它根本不关心你是文本还是图片;对它来说,一切都是字节流。如果你用FileStream操作文本文件,你得自己把字符串转成字节(用编码,比如UTF-8)写进去,读出来时再转回字符串。

什么时候一定要用FileStream?

  • 处理超大文件:当文件大到根本没法或者没必要一次性读进内存(比如几G的日志、视频文件)。FileStream可以分块读写数据,内存用得更高效。
  • 二进制数据:如果你搞的不是普通文本(比如图片、音频、视频、序列化对象、数据库文件),FileStream就是你的主力,因为它能直接操作字节。
  • 精细控制访问模式:你想同时读*和*写?或者想让别的程序根本打不开这个文件?还是想让多个进程一起读?FileStream都能让你完全掌控这些模式。
  • 异步操作:在现代高性能应用(比如web服务器)里,文件操作不能卡主主线程很重要。FileStream支持异步方法(ReadAsync、WriteAsync),让你的程序一直很灵活。
  • 部分读写或随机访问:你想从文件某个位置(比如500字节处)读数据,或者往文件中间写?FileStream可以让你随意移动文件指针。

新手常犯的错:用FileStream去做最简单的事,比如往小文件里写一行文本。这种场景用它就太重了。FileStream适合更复杂、更特殊的需求。

2. 创建和打开FileStream

想用FileStream操作文件,得先new一个实例。构造函数有几个重要参数,决定你怎么和文件打交道。

using System;
using System.IO;   // 用FileStream必须加这个
using System.Text; // 处理编码(字符串和字节互转)

用FileStream基础读取文本文件的例子:

先创建个文件,方便后面读取。

// 创建一个简单的文本文件做演示
string path = "example_filestream.txt";

// 打开流准备读取
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

// 创建缓冲区(字节数组)临时存读取到的数据
byte[] buffer = new byte[fs.Length];

// 从流里读字节到缓冲区
int bytesRead = fs.Read(buffer, 0, buffer.Length);

// 用指定编码(比如UTF-8)把字节转回字符串
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("通过FileStream从文件读取到: " + content);

fs.Close(); //关闭流

主要参数

  • FileMode:怎么打开文件(Open、Create、Append、OpenOrCreate等)
  • FileAccess:对文件做什么(Read、Write、ReadWrite)
  • FileShare:别的进程能不能同时用这个文件(一般简单场景用不到)

常见错误:如果你忘了关闭FileStream,文件可能会“被锁住”——你之后删不了也打不开!

3. FileStream构造函数参数

FileStream的构造函数可以很细致地设置你想怎么打开文件。主要参数如下:

new FileStream(
    string path,           // 文件路径(绝对或相对)
    FileMode mode,         // 怎么打开文件(创建、打开、覆盖等)
    FileAccess access,     // 允许什么操作(只读、只写、读写)
    FileShare share        // 别的进程怎么访问这个文件(可选)
);

下面来看看FileMode和FileAccess枚举的取值:

FileMode(文件打开模式):

决定操作系统打开文件时怎么处理。

  • FileMode.Open:打开已存在的文件。如果找不到文件,会抛出FileNotFoundException。
  • FileMode.Create:创建新文件。如果同名文件已存在,会完全覆盖(内容全没了)。

FileAccess(文件访问权限):

决定你程序对打开的文件能做什么。

  • FileAccess.Read:文件只可读,不能写。
  • FileAccess.Write:文件只可写,不能读。
  • FileAccess.ReadWrite:文件可读可写。最灵活,但你得自己管理流的位置。

FileShare(共享访问):

决定别的进程在你程序打开文件时能不能也打开。很重要,能防止锁死。

  • FileShare.Read:别的进程可以读文件,但不能写。
  • FileShare.Write:别的进程可以写文件,但不能读。
  • FileShare.ReadWrite:别的进程可以读写文件。最开放,但如果多个程序同时改文件可能会冲突。

更多模式细节下节课讲。

4. 用FileStream读写数据

用FileStream时,你操作的都是字节。这意味着处理文本时要用编码类(比如System.Text.Encoding.UTF8)把字符串和字节数组互转。

用FileStream写数据到文件

写数据时,你先把数据(比如字符串)转成字节数组,再写进流。

string outputPath = "user_data.txt";
string userName = "伊万 彼得罗夫";

// 用UTF-8编码把字符串转成字节数组
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);

// 打开FileStream准备写入
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

// 把字节数组写进流
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"名字 '{userName}' 已成功写入文件 '{outputPath}'。");

fsWrite.Close(); //关闭流

用FileStream从文件读数据

读数据时,你把字节读到缓冲区,再转成你想要的格式(比如字符串)。

string inputPath = "user_data.txt";

// 打开FileStream准备读取
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);

// 创建和文件一样大的缓冲区
byte[] buffer = new byte[fsRead.Length];

// 从流里读字节到缓冲区
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);

// 把读到的字节转成字符串
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"通过FileStream从文件读取到名字: {loadedUserName}");

fsRead.Close(); //关闭流

5. 操作大文件

FileStream比File.ReadAll...系列方法最大的优势就是能分块读写,这对内存装不下的大文件特别重要。

分块读取大文件

分块读文件时,你用固定大小的缓冲区(字节数组),每次读一块,直到文件结尾。

string bigFilePath = "bigfile.bin";     //大文件
int bufferSize = 4096;                  // 缓冲区大小,比如4KB
byte[] buffer = new byte[bufferSize];   // 创建缓冲区
int bytesRead;                          // 实际读到的字节数
long totalBytesRead = 0;                // 总共读了多少字节

// 打开流
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;

// 只要fs.Read()返回正数就继续循环
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    totalBytesRead += bytesRead;
    Console.WriteLine($"第{chunkNumber++}块: 读了{bytesRead}字节。总共: {totalBytesRead}字节。");

    // !!! 这里可以处理每一块读到的数据 !!!
   
}

fs.Close(); //关闭流

重要提示:fs.Read(buffer, offset, count)不保证会把buffer填满。它返回实际读到的字节数,可能比count小(尤其到文件结尾时)。一定要用bytesRead的返回值来正确处理数据。

分块写入大数据

和读一样,你也可以分块把大量数据写进文件。

string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // 一百万个'A'的字符串
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // 每次写1KB

// FileMode.Create: 创建文件
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);

for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
    // 计算本次要写多少字节
    int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);

    // 从longTextBytes的i位置开始写bytesToWrite个字节
    fsWrite.Write(longTextBytes, i, bytesToWrite);
    Console.WriteLine($"写入了{bytesToWrite}字节");
}

fsWrite.Close(); //关闭流

6. 不容易发现的坑和常见错误

就算用FileStream这么强的工具,也有不少细节要注意:

缓冲和Flush():前面说过,数据可能会先留在内存缓冲区,没马上写到硬盘。如果你不用using,也没调用Close()/Dispose(),直接让程序结束,数据可能会丢。一定要调用Flush()(或者用using/Close())保证数据写到磁盘。

异常处理:输入/输出错误(比如磁盘满时的IOException,权限不够时的UnauthorizedAccessException,用Open模式打开不存在的文件时的FileNotFoundException)很常见。所有FileStream操作都要用try-catch包起来。

编码:用FileStream处理文本数据(它只认字节),你得自己选对编码(Encoding.UTF8、Encoding.ASCII、Encoding.Unicode等)把字符串和字节互转。选错编码就会出现乱码。

流位置管理:用Seek()时,参数offset和origin要小心,别跳到文件外面或者奇怪的位置。

文件锁(FileShare):如果你用FileShare.None打开文件,别的进程就打不开它。如果别的程序(甚至你自己程序的另一个线程)想用同一个文件就会出问题。一定要选合适的FileShare模式。

2
任务
C# SELF, 第 35 级, 课程 3
已锁定
使用 FileStream 读取文本文件
使用 FileStream 读取文本文件
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION