C#中使用FileStream与MemoryStream操作字节数组详解
在C#开发中,经常需要对文件的二进制数据进行底层操作。System.IO.Stream是所有流的抽象基类,而 FileStream 和 MemoryStream 是其最常用的实现。本文将详细解析如何使用这两个类来读取、写入和处理 byte[](字节数组),包括基础读写、分块处理、异步操作以及内存流的高级用法。
一、Stream 类概述
在 .NET Framework 中,文件是存储在磁盘上的数据集,具有路径和名称;而流(Stream)则是为数据访问提供统一接口的抽象概念。流不仅限于磁盘文件,还可以代表网络数据、内存数据等。
1. Stream 基类属性与方法
Stream 是所有流的抽象基类,核心特性如下:
- 属性:CanRead(是否可读)、CanWrite(是否可写)、CanSeek(是否支持定位/查找)、Length(流长度)、Position(当前流位置)等。
- 方法:Read/Write(字节序列读写)、Seek(设置查找位置)、Flush(清除缓冲区)、Close(关闭流)。
2. 常见流类型对比
- FileStream:主要用于以流的形式对磁盘文件进行读写,支持管道、标准输入输出等操作。
- MemoryStream:在内存中创建流,无需在硬盘上创建临时文件,适合暂时缓存数据。
- BufferedStream:提供缓冲功能,通过减少操作系统调用次数来提高读写效率。
- NetworkStream:为网络访问提供基础数据流。
二、文件流类 FileStream 的使用
FStream 公开了以文件为主的 Stream 功能,直接操作字节和字节数组,非常适合处理非文本的二进制文件。
第1步:完整读文件
适用于小文件。将文件内容一次性读取到字节数组中。
// 创建 d:\file.txt 的 FileStream 对象,使用 FileOpenOrCreate 模式
using (FileStream fstream = new FileStream(@"d:\file.txt", FileMode.OpenOrCreate))
{
// 创建字节数组,长度等于文件长度
byte[] bData = new byte[fstream.Length];
// 重置流当前位置为文件开始
fstream.Seek(0, SeekOrigin.Begin);
// 读取全部内容到字节数组
fstream.Read(bData, 0, bData.Length);
// 解码显示 (注意:若文件非UTF-8编码,需调整 Encoding)
string result = Encoding.UTF8.GetString(bData);
Console.WriteLine(result);
}
第2步:分块读取文件
对于大文件,建议分块读取以避免内存溢出或IO阻塞。
int bufferSize = 4096; // 设定合适的缓冲区大小,如 4KB
// 只读模式打开文件
using (FileStream fstream = new FileStream(@"d:\file.txt", FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize))
{
byte[] bData = new byte[bufferSize];
int bytesRead;
// 循环读取直到返回值为 0
while ((bytesRead = fstream.Read(bData, 0, bData.Length)) > 0)
{
string chunk = Encoding.UTF8.GetString(bData, 0, bytesRead);
Console.WriteLine(chunk);
}
}
第3步:异步读写文件
利用 BeginRead 和 EndRead 或非阻塞的 ReadAsync 提升吞吐量。这里演示基于回调的传统异步模式:
ManualResetEvent mEvent = new ManualResetEvent(false);
int bufferSize = 4096;
byte[] bData = new byte[bufferSize];
using (FileStream fstream = new FileStream(@"d:\file.txt", FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true))
{
AsyncCallback callback = null;
callback = (IAsyncResult ar) =>
{
int bytesRead = fstream.EndRead(ar);
if (bytesRead > 0)
{
string result = Encoding.UTF8.GetString(bData, 0, bytesRead);
Console.WriteLine(result);
// 继续读取下一块
fstream.BeginRead(bData, 0, bufferSize, callback, null);
}
else
{
Console.WriteLine("读取完成");
mEvent.Set();
}
};
// 发起首次异步读取
IAsyncResult async = fstream.BeginRead(bData, 0, bufferSize, callback, null);
// 等待异步操作完成(超时5秒)
mEvent.WaitOne(5000, false);
}
第4步:向文件写入数据
将字符串转为字节数组后写入文件。
using (FileStream fstream = new FileStream(@"d:\write_file.txt", FileMode.Create))
{
byte[] bData = Encoding.UTF8.GetBytes("Hello FileStream!");
fstream.Write(bData, 0, bData.Length);
fstream.Flush(); // 确保数据真正写入磁盘
}
三、内存流 MemoryStream 进阶应用
MemoryStream 在内存中维护一个字节数组,容量可以自动增长。常用于图像解码、数据库存储、加密传输等场景。
示例:在内存中进行读写操作
UnicodeEncoding uniEncoding = new UnicodeEncoding();
// 准备要写入的数据
byte[] firstString = uniEncoding.GetBytes("First part: ");
byte[] secondString = uniEncoding.GetBytes("Second part.");
using (MemoryStream memStream = new MemoryStream())
{
// 写入第一个字符串
memStream.Write(firstString, 0, firstString.Length);
// 逐个字节写入第二个字符串
foreach(byte b in secondString)
{
memStream.WriteByte(b);
}
Console.WriteLine($"Capacity={memStream.Capacity}, Length={memStream.Length}");
// 重置指针到开头进行读取
memStream.Position = 0;
byte[] buffer = new byte[memStream.Length];
memStream.Read(buffer, 0, buffer.Length);
char[] chars = uniEncoding.GetChars(buffer);
Console.WriteLine(new string(chars));
}
Toarray() 与 GetBuffer() 的核心区别
这是面试和实际应用中的高频考点:
ToArray():创建一个与流数据完全匹配的新字节数组副本。安全,但会产生内存分配开销(略慢)。GetBuffer():返回用于构造该流的底层原始字节数组引用。速度极快且无额外内存分配,但数组的Length可能大于实际使用的数据量(存在空白字节)。如果修改这个缓冲区,原 MemoryStream 的数据也会随之改变,需注意生命周期管理。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









