C#?MemoryStream的具體使用
前言
在.NET開發(fā)中,流(Stream)是一個用于處理輸入和輸出的抽象類,MemoryStream是流的一個具體實現(xiàn),它允許我們在內(nèi)存中讀寫數(shù)據(jù),就像操作文件一樣,而無需涉及磁盤 I/O 操作。尤其適合需要快速讀寫、轉(zhuǎn)換或傳輸數(shù)據(jù)的場景。本文將詳細講解MemoryStream的使用。
一、什么是 MemoryStream?
1. 定義
MemoryStream 是 System.IO 命名空間中的一個類,它允許我們在內(nèi)存中創(chuàng)建可讀寫的流。與文件流或網(wǎng)絡(luò)流不同,MemoryStream的數(shù)據(jù)存儲在內(nèi)存中,它不需要依賴物理文件,因此讀寫速度非???,適合處理臨時數(shù)據(jù)(如網(wǎng)絡(luò)傳輸、臨時緩存、序列化對象等)。但會占用一定的內(nèi)存資源。
??MemoryStream 是 System.IO 命名空間中的一個類,它實現(xiàn)了 Stream 抽象類,提供了一系列用于操作數(shù)據(jù)流的屬性和方法。
2. 繼承關(guān)系

2. 核心特性
- 內(nèi)存高效:數(shù)據(jù)直接存儲在內(nèi)存中,無需磁盤 I/O,讀寫速度快。
- 靈活操作:支持讀寫、重置位置、轉(zhuǎn)換為字節(jié)數(shù)組等數(shù)據(jù)處理操作。
- 輕量級:無需文件句柄,適合小到中等規(guī)模的數(shù)據(jù)。
3. 用途
- 處理大量數(shù)據(jù),如圖像、音頻和視頻文件等二進制數(shù)據(jù)。
- 臨時存儲數(shù)據(jù),如網(wǎng)絡(luò)傳輸過程中的數(shù)據(jù)緩沖。
- 實現(xiàn)自定義數(shù)據(jù)流邏輯,例如加密或壓縮數(shù)據(jù)。
4. 為什么需要 MemoryStream?
在數(shù)據(jù)處理場景中,頻繁的磁盤IO操作(如讀寫文件)會顯著降低程序性能,尤其是面對海量數(shù)據(jù)或高頻讀寫需求時。MemoryStream作為C#中的內(nèi)存流,將數(shù)據(jù)存儲在內(nèi)存而非硬盤中,避免了磁盤IO瓶頸,讀寫速度更快。它適用于網(wǎng)絡(luò)數(shù)據(jù)傳輸、臨時緩存、二進制數(shù)據(jù)處理等場景,是實現(xiàn)高性能代碼的利器!
二、基礎(chǔ)用法
1. 創(chuàng)建 MemoryStream 對象
MemoryStream有多個構(gòu)造函數(shù),可以根據(jù)需要選擇合適的構(gòu)造函數(shù)來初始化MemoryStream。
1)無參構(gòu)造函數(shù)
使用無參構(gòu)造函數(shù)可以創(chuàng)建一個空白的 MemoryStream 對象,其初始容量為 0,隨著數(shù)據(jù)寫入自動擴展。
using System.IO; MemoryStream memoryStream = new MemoryStream();
2)帶參構(gòu)造函數(shù)
使用帶參構(gòu)造函數(shù)可以根據(jù)指定的容量創(chuàng)建 MemoryStream 對象,或者從一個字節(jié)數(shù)組創(chuàng)建。
? 指定初始容量的構(gòu)造函數(shù)
MemoryStream memoryStream = new MemoryStream(1024);
創(chuàng)建一個初始容量為1024字節(jié)的MemoryStream。
? 使用字節(jié)數(shù)組初始化的構(gòu)造函數(shù)
byte[] buffer = new byte[] { 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33 };
MemoryStream ms = new MemoryStream(buffer);
使用現(xiàn)有的字節(jié)數(shù)組初始化MemoryStream。
2. 寫入數(shù)據(jù)
MemoryStream 提供了多種方法來寫入數(shù)據(jù),最常用的是 Write 方法和 WriteByte 方法。
1)寫入字節(jié)數(shù)組
使用 Write 方法可以將一個字節(jié)數(shù)組寫入 MemoryStream。
byte[] data = new byte[] { 72, 101, 108, 108, 111 };
memoryStream.Write(data, 0, data.Length);
2)寫入字符串
向 MemoryStream 寫入一個字符串,需要將字符串轉(zhuǎn)換為字節(jié)數(shù)組。
string text = "Hello, World!"; byte[] data = System.Text.Encoding.UTF8.GetBytes(text); memoryStream.Write(data, 0, data.Length);
3)使用 WriteByte 方法
WriteByte 方法可以逐字節(jié)寫入數(shù)據(jù)。
string text = "Hello, World!";
byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
foreach (byte b in data)
{
memoryStream.WriteByte(b);
}
3. 讀取數(shù)據(jù)
MemoryStream 提供了多種方法來讀取數(shù)據(jù),最常用的是 Read 方法和 ReadByte 方法。
1)讀取字節(jié)數(shù)組
從 MemoryStream 讀取一定數(shù)量的字節(jié)到字節(jié)數(shù)組中。
byte[] buffer = new byte[11]; int bytesRead = memoryStream.Read(buffer, 0, buffer.Length);
2)讀取字符串
從 MemoryStream 讀取一定數(shù)量的字節(jié),然后將其轉(zhuǎn)換為字符串。
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 方法可以逐字節(jié)讀取數(shù)據(jù)。
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
獲取或設(shè)置分配給MemoryStream 的字節(jié)數(shù)
MemoryStream memoryStream = new MemoryStream(1024); int capacity = memoryStream.Capacity; // 輸出:1024
2)Length
獲取MemoryStream中實際使用的數(shù)據(jù)長度。
MemoryStream memoryStream = new MemoryStream(1024); long length = memoryStream.Length; // length = 0
3)Position
獲取或設(shè)置MemoryStream的當(dāng)前讀寫位置。
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 設(shè)置長度
使用 SetLength 方法可以設(shè)置 MemoryStream 的長度,如果新長度小于當(dāng)前長度,數(shù)據(jù)將被截斷;如果新長度大于當(dāng)前長度,數(shù)據(jù)將被擴展。
memoryStream.SetLength(50);
2)Seek 設(shè)置當(dāng)前讀寫位置
ms.Seek(0, SeekOrigin.Begin);
移動MemoryStream的當(dāng)前讀寫位置。
3)ToArray 轉(zhuǎn)換為字節(jié)數(shù)組
使用 ToArray 方法可以將 MemoryStream 的內(nèi)容轉(zhuǎn)換為字節(jié)數(shù)組。
byte[] allBytes = memoryStream.ToArray();
4)GetBuffer 獲取底層緩沖區(qū)的字節(jié)數(shù)組
byte[] buffer = memoryStream.GetBuffer();
GetBuffer方法返回底層緩沖區(qū)的完整字節(jié)數(shù)組(包含未使用的空間),ToArray方法返回僅包含有效數(shù)據(jù)的數(shù)組(排除未使用的空間)。
關(guān)于 ToArray 和 GetBuffer 方法的區(qū)別,詳見:C# MemoryStream 中 ToArray 和 GetBuffer 的區(qū)別
6. Position 注意事項
1)寫入數(shù)據(jù)后的 Position 自動前進
// 創(chuàng)建空的 MemoryStream
using (MemoryStream ms = new MemoryStream())
{
// 寫入字符串
byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello, MemoryStream!");
ms.Write(data, 0, data.Length);
// 寫入后的位置自動前進
Console.WriteLine($"當(dāng)前位置:{ms.Position}"); // 輸出:20(假設(shè) UTF-8 編碼)
}
2)讀取數(shù)據(jù) 必須重置 Position
using (MemoryStream ms = new MemoryStream())
{
// 寫入數(shù)據(jù)后重置位置到開頭
ms.Write(data, 0, data.Length);
ms.Position = 0; // 必須重置位置才能讀取
// 讀取數(shù)據(jù)
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()調(diào)整指針位置,如 stream.Seek(0, SeekOrigin.Begin)。
7. 示例代碼
下面是一個完整的示例,演示了如何使用 MemoryStream:
public class Program
{
public static void Main(string[] args)
{
// 創(chuàng)建 MemoryStream
MemoryStream memoryStream = new MemoryStream();
// 獲取當(dāng)前讀寫的位置
Console.WriteLine($"MemoryStream Position: {memoryStream.Position}");// 輸出:MemoryStream Position: 0
string text = "Hello, World!";
byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
// 寫入數(shù)據(jù)
memoryStream.Write(data, 0, data.Length);
// 轉(zhuǎn)換為字節(jié)數(shù)組
Console.WriteLine(BitConverter.ToString(memoryStream.ToArray())); //輸出:48-65-6C-6C-6F-2C-20-57-6F-72-6C-64-21
// 設(shè)置長度
memoryStream.SetLength(50);
// 獲取長度
Console.WriteLine($"MemoryStream length: {memoryStream.Length}"); // 輸出:MemoryStream length: 50
// 獲取當(dāng)前讀寫的位置
Console.WriteLine($"MemoryStream Position: {memoryStream.Position}");// 輸出:MemoryStream Position: 13
// 讀取數(shù)據(jù)
byte[] buffer = new byte[5];
int bytesRead = memoryStream.Read(buffer, 0, buffer.Length);
// 輸出結(jié)果
Console.WriteLine(BitConverter.ToString(buffer)); // 輸出:00-00-00-00-00
// 定位
memoryStream.Position = 0;
// 再次讀取數(shù)據(jù)
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 在處理內(nèi)存中的數(shù)據(jù)流時是多么靈活和有用。它不僅可以用于臨時存儲數(shù)據(jù),還可以用于實現(xiàn)復(fù)雜的數(shù)據(jù)處理邏輯。
三、MemoryStream的高級使用
1. 數(shù)據(jù)交互
1)與文本數(shù)據(jù)交互
使用 StreamReader/StreamWriter
public class Program
{
public static void Main(string[] args)
{
using (MemoryStream ms = new MemoryStream())
{
// 創(chuàng)建一個StreamWriter,用于向MemoryStream寫入字符串
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8, 1024, leaveOpen: true))
{
// leaveOpen: true 的作用:
// 防止 StreamWriter 關(guān)閉時連帶關(guān)閉底層的 MemoryStream,確保后續(xù) StreamReader 可正常操作流
sw.WriteLine("Hello, World!");
sw.WriteLine("This is a test.");
}
// 將MemoryStream的位置重置到開頭
ms.Seek(0, SeekOrigin.Begin);
// 創(chuàng)建一個StreamReader,用于從MemoryStream讀取字符串
using (StreamReader sr = new StreamReader(ms, Encoding.UTF8))
{
string line;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
}
在這個例子中,我們首先創(chuàng)建了一個MemoryStream實例,然后使用StreamWriter向MemoryStream寫入了兩行字符串。寫入完成后,我們將MemoryStream的位置重置到開頭,接著使用StreamReader從MemoryStream讀取字符串并打印到控制臺。
2)與二進制數(shù)據(jù)交互
使用 BinaryReader/BinaryWriter
public class Program
{
public static void Main(string[] args)
{
using (MemoryStream ms = new MemoryStream())
{
// 創(chuàng)建一個BinaryWriter,用于向MemoryStream寫入二進制數(shù)據(jù)
using (BinaryWriter writer = new BinaryWriter(ms,Encoding.UTF8,leaveOpen:true))
{
// leaveOpen: true 的作用:
// 防止 StreamWriter 關(guān)閉時連帶關(guān)閉底層的 MemoryStream,確保后續(xù) StreamReader 可正常操作流
writer.Write(32);// 寫入整數(shù)
writer.Write(12.3f);// 寫入單精度浮點數(shù)
writer.Write("This is a test.");// 寫入字符串
}
// 將MemoryStream的位置重置到開頭
ms.Seek(0, SeekOrigin.Begin);
// 創(chuàng)建一個 BinaryReader,用于從MemoryStream讀取二進制數(shù)據(jù)
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)與網(wǎng)絡(luò)流交互
將內(nèi)存流作為網(wǎng)絡(luò)傳輸?shù)木彌_區(qū):
// 服務(wù)端接收數(shù)據(jù) NetworkStream ns = client.GetStream(); MemoryStream ms = new MemoryStream(); ns.CopyTo(ms); // 將網(wǎng)絡(luò)流復(fù)制到內(nèi)存流 byte[] buffer = ms.ToArray();
2. 高級技巧
1)及時釋放資源
使用using語句:確保流對象及時釋放,避免內(nèi)存泄漏。
using (MemoryStream stream = new MemoryStream()) { /*...*/ }
2)設(shè)置初始容量
預(yù)分配容量:若已知數(shù)據(jù)大小,初始化時指定Capacity減少動態(tài)擴容開銷。
頻繁寫入數(shù)據(jù)時,指定初始容量可避免內(nèi)存頻繁擴容:
// 預(yù)分配 1MB 內(nèi)存
using (MemoryStream ms = new MemoryStream(1024 * 1024))
{
// 寫入大量數(shù)據(jù)
}
3)重置流以復(fù)用內(nèi)存
通過 SetLength(0) 和 Seek 方法或 設(shè)置Position重置流:
using (MemoryStream ms = new MemoryStream())
{
ms.Write(data, 0, data.Length);
// 重置流并清空內(nèi)容
ms.SetLength(0);
ms.Position = 0; //或 ms.Seek(0, SeekOrigin.Begin);
// 重新寫入新數(shù)據(jù)
ms.Write(newData, 0, newData.Length);
}
3. 實際應(yīng)用示例
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作為臨時緩沖區(qū)
using System;
using System.IO;
public class MemoryStreamExample
{
public static void Main()
{
// 創(chuàng)建一個MemoryStream作為臨時緩沖區(qū)
using (MemoryStream ms = new MemoryStream())
{
// 寫入一些數(shù)據(jù)到MemoryStream
byte[] data = new byte[] { 1, 2, 3, 4, 5 };
ms.Write(data, 0, data.Length);
// 將MemoryStream作為參數(shù)傳遞給其他方法
ProcessData(ms);
}
}
public static void ProcessData(MemoryStream ms)
{
// 將MemoryStream的位置重置到開頭
ms.Seek(0, SeekOrigin.Begin);
// 讀取數(shù)據(jù) 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)高效處理復(fù)雜場景
案例:日志文件關(guān)鍵字篩選
假設(shè)需要從多個.log文件中提取含特定關(guān)鍵字的行,傳統(tǒng)方法可能導(dǎo)致內(nèi)存暴漲。
優(yōu)化方案:
- 逐行讀取文件:使用
StreamReader避免一次性加載大文件。 - 內(nèi)存流緩存匹配行:將匹配的行暫存至
MemoryStream,減少磁盤IO次數(shù)。 - 批量寫入結(jié)果:最后將內(nèi)存流數(shù)據(jù)一次性寫入目標(biāo)文件。實測性能提升顯著
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合并數(shù)據(jù)并寫入文件 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. 讀取時超出容量
// 錯誤示例:未重置位置導(dǎo)致讀取失敗
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. 處理大文件時的內(nèi)存問題
當(dāng)數(shù)據(jù)量超過內(nèi)存限制時,改用 FileStream:
// 替代方案:使用文件流
using (FileStream fs = new FileStream("temp.bin", FileMode.Create))
{
// 寫入數(shù)據(jù)到文件流
}
3. 異步操作
通過 ToArray() 獲取字節(jié)數(shù)組后,可異步處理:
public async Task ProcessAsync()
{
using (MemoryStream ms = new MemoryStream())
{
// 寫入數(shù)據(jù)
byte[] data = ms.ToArray();
// 異步發(fā)送到網(wǎng)絡(luò)
await client.SendAsync(data);
}
}
五、MemoryStream的優(yōu)缺點
優(yōu)點
- 內(nèi)存中操作速度快:由于數(shù)據(jù)存儲在內(nèi)存中,讀寫速度非常快。
- 容量靈活:
MemoryStream的容量可以動態(tài)增長,以適應(yīng)數(shù)據(jù)量的變化。 - 支持讀寫定位操作:支持
CanRead、CanWrite和CanSeek屬性,便于靈活操作。
缺點
- 內(nèi)存占用高:處理大量數(shù)據(jù)時,可能會占用大量的內(nèi)存資源。
- 不適合持久化存儲:數(shù)據(jù)存儲在內(nèi)存中,程序關(guān)閉后數(shù)據(jù)會丟失。
六、最佳實踐總結(jié)
資源管理
始終使用using語句確保流正確釋放:using (MemoryStream ms = new MemoryStream()) { ... }位置重置
寫入后讀取前必須重置Position到0。性能優(yōu)化
- 指定初始容量:在創(chuàng)建
MemoryStream時,盡量指定初始容量,以減少動態(tài)增長的次數(shù),提高性能。 - 處理大量數(shù)據(jù)時謹(jǐn)慎使用:處理大量數(shù)據(jù)時,考慮使用文件流或其他適合的流類型,避免內(nèi)存占用過高。(對大文件使用
FileStream替代。)
- 指定初始容量:在創(chuàng)建
參考資料
C# MemoryStream流的詳解與示例
C# Stream 和 byte[] 之間的轉(zhuǎn)換(文件流的應(yīng)用)
C# Stream篇(五) – MemoryStream
到此這篇關(guān)于C# MemoryStream 使用詳解的文章就介紹到這了,更多相關(guān)C# MemoryStream 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WPF實現(xiàn)在控件上顯示Loading等待動畫的方法詳解
這篇文章主要介紹了WPF 如何在控件上顯示 Loading 等待動畫,文中的示例代碼講解詳細,對我們學(xué)習(xí)或工作有一定幫助,需要的可以參考一下2023-03-03
漢字轉(zhuǎn)拼音縮寫示例代碼(Silverlight和.NET 將漢字轉(zhuǎn)換成為拼音)
本篇文章主要介紹了漢字轉(zhuǎn)拼音縮寫示例代碼(Silverlight和.NET 將漢字轉(zhuǎn)換成為拼音) 需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01
C#使用RabbitMQ發(fā)送和接收消息工具類的實現(xiàn)
RabbitMQ是一個消息的代理器,用于接收和發(fā)送消息,本文主要介紹了C#使用RabbitMQ發(fā)送和接收消息工具類的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-12-12

