目录
- 前言
- 一、什么是 MemoryStream?
- 1. 定义
- 2. 继承关系
- 2. 核心特性
- 3. 用途
- 4. 为什么需要 MemoryStream?
- 二、基础用法
- 1. 创建 MemoryStream 对象
- 1)无参构造函数
- 2)带参构造函数
- 2. 写入数据
- 1)写入字节数组
- 2)写入字符串
- 3)使用 WriteByte 方法
- 3. 读取数据
- 1)读取字节数组
- 2)读取字符串
- 3)使用 ReadByte 方法
- 4. 常用属性
- 1)Capacity
- 2)Length
- 3)Position
- 4)CanRead、CanWrite、CanSeek
- 5. 常用辅助方法
- 1)SetLength 设置长度
- 2)Seek 设置当前读写位置
- 3)ToArray 转换为字节数组
- 4)GetBuffer 获取底层缓冲区的字节数组
- 6. Position 注意事项
- 1)写入数据后的 Position 自动前进
- 2)读取数据 必须重置 Position
- 7. 示例代码
- 三、MemoryStream的高级使用
- 1. 数据交互
- 1)与文本数据交互
- 2)与二进制数据交互
- 3)与网络流交互
- 2. 高级技巧
- 1)及时释放资源
- 2)设置初始容量
- 3)重置流以复用内存
- 3. 实际应用示例
- 1)使用MemoryStream序列化和反序列化对象
- 2)使用MemoryStream作为临时缓冲区
- 3)高效处理复杂场景
- 四、常见问题与解决方案
- 1. 读取时超出容量
- 2. 处理大文件时的内存问题
- 3. 异步操作
- 五、MemoryStream的优缺点
- 六、最佳实践总结
- 参考资料
前言
在.NET开发中,流(Stream)是一个用于处理输入和输出的抽象类,MemoryStream
是流的一个具体实现,它允许我们在内存中读写数据,就像操作文件一样,而无需涉及磁盘 I/O 操作。尤其适合需要快速读写、转换或传输数据的场景。本文将详细讲解MemoryStream
的使用。
一、什么是 MemoryStream?
1. 定义
MemoryStream
是 System.IO
命名空间中的一个类,它允许我们在内存中创建可读写的流。与文件流或网络流不同,MemoryStream
的数据存储在内存中,它不需要依赖物理文件,因此读写速度非常快,适合处理临时数据(如网络传输、临时缓存、序列化对象等)。但会占用一定的内存资源。
MemoryStream 是 System.IO 命名空间中的一个类,它实现了 Stream 抽象类,提供了一系列用于操作数据流的属性和方法。
2. 继承关系
2. 核心特性
- 内存高效:数据直接存储在内存中,无需磁盘 I/O,读写速度快。
- 灵活操作:支持读写、重置位置、转换为字节数组等数据处理操作。
- 轻量级:无需文件句柄,适合小到中等规模的数据。
3. 用途
- 处理大量数据,如图像、音频和视频文件等二进制数据。
- 临时存储数据,如网络传输过程中的数据缓冲。
- 实现自定义数据流逻辑,例如加密或压缩数据。
4. 为什么需要 MemoryStream?
在数据处理场景中,频繁的磁盘IO操作(如读写文件)会显著降低程序性能,尤其是面对海量数据或高频读写需求时。MemoryStream作为C#中的内存流,将数据存储在内存而非硬盘中,避免了磁盘IO瓶颈,读写速度更快。它适用于网络数据传输、临时缓存、二进制数据处理等场景,是实现高性能代码的利器!
二、基础用法
1. 创建 MemoryStream 对象
MemoryStream
有多个构造函数,可以根据需要选择合适的构造函数来初始化MemoryStream
。
1)无参构造函数
使用无参构造函数可以创建一个空白的 MemoryStream
对象,其初始容量为 0,随着数据写入自动扩展。
using System.IO; MemoryStream memoryStream = new MemoryStream();
2)带参构造函数
使用带参构造函数可以根据指定的容量创建 MemoryStream
对象,或者从一个字节数组创建。
▶ 指定初始容量的构造函数
MemoryStream memoryStream = new MemoryStream(1024);
创建一个初始容量为1024字节的MemoryStream
。
▶ 使用字节数组初始化的构造函数
byte[] buffer = new byte[] { 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33 }; MemoryStream ms = new MemoryStream(buffer);
使用现有的字节数组初始化MemoryStream
。
2. 写入数据
MemoryStream
提供了多种方法来写入数据,最常用的是 Write
方法和 WriteByte
方法。
1)写入字节数组
使用 Write
方法可以将一个字节数组写入 MemoryStream
。
byte[] data = new byte[] { 72, 101, 108, 108, 111 }; memoryStream.Write(data, 0, data.Length);
2)写入字符串
向 MemoryStream
写入一个字符串,需要将字符串转换为字节数组。
string text = "Hello, World!"; byte[] data = System.Text.Encoding.UTF8.GetBytes(text); memoryStream.Write(data, 0, data.Length);
3)使用 WriteByte 方法
WriteByte
方法可以逐字节写入数据。
string text = "Hello, World!"; byte[] data = System.Text.Encoding.UTF8.GetBytes(text); foreach (byte b in data) { memoryStream.WriteByte(b); }
3. 读取数据
MemoryStream
提供了多种方法来读取数据,最常用的是 Read
方法和 ReadByte
方法。
1)读取字节数组
从 MemoryStream
读取一定数量的字节到字节数组中。
byte[] buffer = new byte[11]; int bytesRead = memoryStream.Read(buffer, 0, buffer.Length);
2)读取字符串
从 MemoryStream
读取一定数量的字节,然后将其转换为字符串。
byte[] buffer = new byte[11]; int bytesRead = memoryStream.Read(buffer, 0, buffer.Length); string text = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
3)使用 ReadByte 方法
ReadByte
方法可以逐字节读取数据。
List<byte> byteList = new List<byte>(); while (memoryStream.Position < memoryStream.Length) { byteList.Add((byte)memoryStream.ReadByte()); } string text = System.Text.Encoding.UTF8.GetString(byteList.ToArray());
4. 常用属性
1)Capacity
获取或设置分配给MemoryStream
的字节数
MemoryStream memoryStream = new MemoryStream(1024); int capacity = memoryStream.Capacity; // 输出:1024
2)Length
获取MemoryStream
中实际使用的数据长度。
MemoryStream memoryStream = new MemoryStream(1024); long length = memoryStream.Length; // length = 0
3)Position
获取或设置MemoryStream
的当前读写位置。
memoryStream.Position = 0; // 定位到流的开头
4)CanRead、CanWrite、CanSeek
bool canRead = ms.CanRead; bool canWrite = ms.CanWrite; bool canSeek = ms.CanSeek;
表示MemoryStream
是否支持读取、写入和定位操作。对于MemoryStream
,这些属性通常返回true
。
5. 常用辅助方法
1)SetLength 设置长度
使用 SetLength
方法可以设置 MemoryStream
的长度,如果新长度小于当前长度,数据将被截断;如果新长度大于当前长度,数据将被扩展。
memoryStream.SetLength(50);
2)Seek 设置当前读写位置
ms.Seek(0, SeekOrigin.Begin);
移动MemoryStream
的当前读写位置。
3)ToArray 转换为字节数组
使用 ToArray
方法可以将 MemoryStream
的内容转换为字节数组。
byte[] allBytes = memoryStream.ToArray();
4)GetBuffer 获取底层缓冲区的字节数组
byte[] buffer = memoryStream.GetBuffer();
GetBuffer
方法返回底层缓冲区的完整字节数组(包含未使用的空间),ToArray
方法返回仅包含有效数据的数组(排除未使用的空间)。
关于 ToArray 和 GetBuffer 方法的区别,详见:C# MemoryStream 中 ToArray 和 GetBuffer 的区别
6. Position 注意事项
1)写入数据后的 Position 自动前进
// 创建空的 MemoryStream using (MemoryStream ms = new MemoryStream()) { // 写入字符串 byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello, MemoryStream!"); ms.Write(data, 0, data.Length); // 写入后的位置自动前进 Console.WriteLine($"当前位置:{ms.Position}"); // 输出:20(假设 UTF-8 编码) }
2)读取数据 必须重置 Position编程
using (MemoryStream ms = new MemoryStream()) { // 写入数据后重置位置到开头 ms.Write(data, 0, data.Length); ms.Position = 0; // 必须重置位置才能读取 // 读取数据 byte[] buffer = new byte[ms.Length]; ms.Read(buffer, 0, (int)ms.Length); string result = System.Text.Encoding.UTF8.GetString(buffer); Console.WriteLine(result); // 输出:Hello, MemoryStream! }
Tips:
除了使用 Position 属性重置位置外,读写前还可用Seek()调整指针位置,如 stream.Seek(0, SeekOrigin.Begin)。
7. 示例代码
下面是一个完整的示例,演示了如何使用 MemoryStream
:
public class Program { public static void Main(string[] args) { // 创建 MemoryStream MemoryStream memoryStream = new MemoryStream(); // 获取当前读写的位置 Console.WriteLine($"MemoryStream Position: {memoryStream.Position}");// 输出:MemoryStream Position: 0 string text = "Hello, World!"; byte[] data = System.Text.Encoding.UTF8.GetBytes(text); // 写入数据 memoryStream.Write(data, 0, data.Length); // 转换为字节数组 Console.WriteLine(BitConverter.ToString(memoryStream.ToArray())); //输出:48-65-6C-6C-6F-2C-20-57-6F-72-6C-64-21 // 设置长度 memoryStream.SetLength(50); // 获取长度 Console.WriteLine($"MemoryStream length: {memoryStream.Length}"); // 输出:MemoryStream length: 50 // 获取当前读写的位置 Console.WriteLine($"MemoryStream Position: {memoryStream.Position}");// 输出:MemoryStream Position: 13 // 读取数据 byte[] buffer = new byte[5]; int bytesRead = memoryStream.Read(buffer, 0, buffer.Length); // 输出结果 Console.WriteLine(BitConverter.ToString(buffer)); // 输出:00-00-00-00-00 // 定位 memoryStream.Position = 0; CpketC// 再次读取数据 bytesRead = memoryStream.Read(buffer, 0, buffer.Length); Console.WriteLine(BitConverter.ToString(buffer)); // 输出:48 65 6C 6C 6F Console.WriteLine(Encoding.UTF8.GetString(buffer)); //输出:Hello // 清空 MemoryStream memoryStream.SetLength(0); memoryStream.Position = 0; // 检查是否清空 Console.WriteLine("MemoryStream length after clear: " + memoryStream.Length); // 输出:MemoryStream length after clear: 0 } }
通过这个示例,我们可以看到 MemoryStream
在处理内存中的数据流时是多么灵活和有用。它不仅可以用于临时存储数据,还可以用于实现复杂的数据处理逻辑。
三、MemoryStream的高级使用
1. 数据交互
1)与文本数据交互
使用 StreamReader
/StreamWriter
public class Progpythonram { public static void Main(string[] args) { using (MemoryStream ms = new MemoryStream()) { // 创建一个StreamWriter,用于向MemoryStream写入字符串 using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8, 1024, leaveOpen: true)) { // leaveOpen: true 的作用: // 防止 StreamWriter 关闭时连带关闭底层的 MemoryStream,确保后续 StreamReader 可正常操作流 sw.WriteLine("Hello, World!"); sw.WriteLine("This is a test."); } // 将MemoryStream的位置重置到开头 ms.Seek(0, SeekOrigin.Begin); // 创建一个StreamReader,用于从MemoryStream读取字符串 using (StreamReader sr = new StreamReader(ms, Encoding.UTF8)) { string line; while ((line = sr.ReadLine()) != null) { Console.WriteLine(line); } } } } }
在这个例子中,我们首先创建了一个MemoryStream
实例,然后使用StreamWriter
向MemoryStream
写入了两行字符串。写入完成后,我们将MemoryStream
的位置重置到开头,接着使用StreamReader
从MemoryStream
读取字符串并打印到控制台。
2)与二进制数据交互
使用 BinaryReader
/BinaryWriter
public class Program { public static void Main(string[] args) { using (MemoryStream ms = new MemoryStream()) { // 创建一个BinaryWriter,用于向MemoryStream写入二进制数据 using (BinaryWriter writer = new BinaryWriter(ms,Encoding.UTF8,leaveOpen:true)) { // leaveOpen: true 的作用: // 防止 StreamWriter 关闭时连带关闭底层的 MemoryStream,确保后续 StreamReader 可正常操作流 writer.Write(32);// 写入整数 writer.Write(12.3f);// 写入单精度浮点数 writer.Write("This is a test.");// 写入字符串 } // 将MemoryStream的位置重置到开头 ms.Seek(0, SeekOrigin.Begin); // 创建一个 BinaryReader,用于从MemoryStream读取二进制数据 using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) { Console.WriteLine(reader.ReadInt32()); //输出:32 Console.WriteLine(reader.ReadSingle()); //输出:12.3 Console.WriteLine(reader.ReadString()); //输出:This is a test. } } } }
3)与网络流交互
将内存流作为网络传输的缓冲区:
// 服务端接收数据 NetworkStream ns = client.GetStream(); MemoryStream ms = new MemoryStream(); ns.CopyTo(ms); // 将网络流复制到内存流 byte[] buffer = ms.ToArray();
2. 高级技巧
1)及时释放资源
使用using
语句:确保流对象及时释放,避免内存泄漏。
using (MemoryStream stream = new MemoryStream()) { /*...*/ }
2)设置初始容量
预分配容量:若已知数据大小,初始化时指定Capacity
减少动态扩容开销。
频繁写入数据时,指定初始容量可避免内存频繁扩容:
// 预分配 1MB 内存 using (MemoryStream ms = new MemoryStream(1024 * 1024)) { // 写入大量数据 }
3)重置流以复用内存
通过 SetLength(0)
和 Seek
方法或 设置Position
重置流:
using (MemoryStream ms = new MemoryStream()) { ms.Write(data, 0, data.Length); // 重置流并清空内容 ms.SetLength(0); ms.Position = 0; //或 ms.Seek(0, SeekOrigin.Begin); // 重新写入新数据 ms.Write(newData, 0, newData.Length); }
3. 实际应用示例
1)使用MemoryStream序列化和反序列化对象
using System; using System.Text; using 编程客栈System.Text.json; public class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main() { Person person = new Person { Name = "John Doe", Age = 30 }; // 序列化对象到MemoryStream using (MemoryStream ms = new MemoryStream()) { JsonSerializer.Serialize(ms, person); // 将MemoryStream的位置重置到开头 ms.Seek(0, SeekOrigin.Begin); // 反序列化对象从MemoryStream Person deserializedPerson = JsonSerializer.Deserialize<Person>(ms); Console.WriteLine("Name: " + deserializedPerson.Name); // 输出:Name: John Doe Console.WriteLine("Age: " + deserializedPerson.Age); // 输出:Age: 30 } } }
2)使用MemoryStream作为临时缓冲区
using System; using System.IO; public class MemoryStreamExample { public static void Main() { // 创建一个MemoryStream作为临时缓冲区 using (MemoryStream ms = new MemoryStream()) { // 写入一些数据到MemoryStream byte[] data = new byte[] { 1, 2, 3, 4, 5 }; ms.Write(data, 0, data.Length); // 将MemoryStream作为参数传递给其他方法 ProcessData(ms); } } public static void ProcessData(MemoryStream ms) { // 将MemoryStream的位置重置到开头 ms.Seek(0, SeekOrigin.Begin); // 读取数据 from MemoryStream byte[] buffer = new byte[ms.Length]; ms.Read(buffer, 0, buffer.Length); Console.WriteLine("Received data:"); foreach (byte b in buffer) { Console.Write(b + " "); } } }
3)高效处理复杂场景
案例:日志文件关键字筛选
假设需要从多个.log
文件中提取含特定关键字的行,传统方法可能导致内存暴涨。
优化方案:
- 逐行读取文件:使用
StreamReader
避免一次性加载大文件。 - 内存流缓存匹配行:将匹配的行暂存至
MemoryStream
,减少磁盘IO次数。 - 批量写入结果:最后将内存流数据一次性写入目标文件。
List<string> matchedLines = new List<string>(); foreach (var file in Directory.GetFiles("logs", "*.log")) { using (var reader = new StreamReader(file)) { while (!reader.EndOfStream) { string line = reader.ReadLine(); if (Regex.IsMatch(line, "keyword")) { matchedLines.Add(line); } } } } // 使用MemoryStream合并数据并写入文件 using (MemoryStream ms = new MemoryStream()) { byte[] buffer = Encoding.UTF8.GetBytes(string.Join("\n", matchedLines)); ms.Write(buffer, 0, buffer.Length); File.WriteAllBytes("result.txt", ms.ToArray()); }
实测性能提升显著
四、常见问题与解决方案
1. 读取时超出容量
// 错误示例:未重置位置导致读取失败 using (MemoryStream ms = new MemoryStream()) { ms.Write(data, 0, data.Length); byte[] buffer = new byte[ms.Length]; ms.Read(buffer, 0, buffer.Length); // 抛出异常,因为 Position 已在末尾 } // 正确做法:重置位置 ms.Position = 0; ms.Read(buffer, 0, buffer.Length);
2. 处理大文件时的内存问题
当数据量超过内存限制时,改用 FileSjstream
:
// 替代方案:使用文件流 using (FileStream fs = new FileStream("temp.bin", FileMode.Create)) { // 写入数据到文件流 }
3. 异步操作
通过 ToArray()
获取字节数组后,可异步处理:
public async Task ProcessAsync() { using (MemoryStream ms = new MemoryStream()) { // 写入数据 byte[] data = ms.ToArray(); // 异步发送到网络 await client.SendAsync(data); } }
五、MemoryStream的优缺点
优点
- 内存中操作速度快:由于数据存储在内存中,读写速度非常快。
- 容量灵活:
MemoryStream
的容量可以动态增长,以适应数据量的变化。 - 支持读写定位操作:支持
CanRead
、CanWrite
和CanSeek
属性,便于灵活操作。
缺点
- 内存占用高:处理大量数据时,可能会占用大量的内存资源。
- 不适合持久化存储:数据存储在内存中,程序关闭后数据会丢失。
六、最佳实践总结
资源管理
始终使用using
语句确保流正确释放:using (MemoryStream ms = new MemoryStream()) { ... }
位置重置
写入后读取前必须重置Position
到0
。性能优化
- 指定初始容量:在创建
MemoryStream
时,尽量指定初始容量,以减少动态增长的次数,提高性能。 - 处理大量数据时谨慎使用:处理大量数据时,考虑使用文件流或其他适合的流类型,避免内存占用过高。(对大文件使用
FileStream
替代。)
- 指定初始容量:在创建
参考资料
C# MemoryStream流的详解与示例
C# Stream 和 byte[] 之间的转换(文件流的应用)C# Stream篇(五) – MemoryStream到此这篇关于C# MemoryStream 使用详解的文章就介绍到这了,更多相关C# MemoryStream 使用内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论