重置或清空.NET MemoryStream

64

.NET的MemoryStream似乎没有.Reset或.Clear方法。

我曾考虑使用以下代码来完成这个操作:

ms.Seek(0, IO.SeekOrigin.Begin)
ms.SetLength(0)

如何正确地清除或重置现有的.NET MemoryStream?


9
只是为了澄清这个问题。我认为他想知道哪种方法可以防止内存被分配。理论上,SetLength(0) 应该保留 Capacity,而分配一个 new MemoryStream() 则应该释放这块内存并分配新的内存。根据使用情况,有些人可能希望保留内存并重置流的 LengthPosition。(无论如何,这就是一个注重内存的 C++ 程序员会考虑的方法)。 - CodeAngry
1
而且 OP 的代码是正确的。.Capacity 在这些指令之后仍然存在,因此这是避免在不必要时进行 free/alloc 的最佳方法。 - CodeAngry
5个回答

104

为什么需要重置内存流?你总是可以创建一个新的。或者你可以使用:

memoryStream.SetLength(0);

虽然我晚了,但安德鲁是正确的。我把这个留在这里,因为我不得不检查。MSDN:如果指定的值大于当前容量并且流是可调整大小的,则增加容量...旧长度和新长度之间的流都被初始化为零。 - Rig
1
当您重新读取数据到流中时,该方法无法正常工作。位置和长度将为0! - Martin.Martinsson
7
创建新的MemoryStream会引起一次内存分配。一些程序对性能非常敏感,每次分配都很重要。 - Nic Foster
@NicFoster memoryStream.SetLength(0) 的目的是什么?我还能再次向流中写入数据吗? - silkfire
@silkfire,它清除了内存流,因此您可以重复使用它,而不是创建一个将分配内存(较慢)的新流。 - Nic Foster
由于容量被保留,如果您向其写入一个更大容量的流,会发生什么情况呢?它会自动增加容量到新的长度,还是会报错? - undefined

34

由于MemoryStream本质上是一个带有索引(以及一些其他支持成员)的字节数组,因此清除字节数组并重置索引可以被认为是重置和清除MemoryStream。如果MemoryStream的初始状态是具有位置零的清零数组,则MemoryStream重置的示例可能如下:

public static void Clear(this MemoryStream source)
{
  byte[] buffer = source.GetBuffer();
  Array.Clear(buffer, 0, buffer.Length);
  source.Position = 0;
  source.SetLength(0);
}

错误的说法是MemoryStream.SetLength单独重置或清除MemoryStream,因为如果长度超过当前缓冲区的长度,SetLength只会清除内部缓冲区数组。

重新初始化MemoryStream是一种有效的方法,但不太高效。重新初始化MemoryStream的一个好处是保证流从未关闭。一旦MemoryStream被关闭,就无法再更改它。如果您可以确保MemoryStream实例未关闭,则清除缓冲区可能是一个好方法。


1
.Reset() 不应该清零数据,对吧?只有 .Clear() 才能做到这一点... 另外,有没有办法在流关闭后读取数据长度?(即:希望不使用私有反射) - user645280
在这里强调一个重要的点:如果您直接使用缓冲区,则在将位置重置为零时,如果不将缓冲区清零,则有读取“旧”数据的风险。但是,如果坚持使用Read()方法,则不会发生这种情况-流的“末尾”之后的字节不会从一个缓冲区复制到另一个缓冲区。 - piers7
2
当调用source.GetBuffer()时,即使是有效的非空流,总是会得到一个异常:_无法访问MemoryStream的内部缓冲区_。更新:在这里解释了原因:https://dev59.com/uHI-5IYBdhLWcg3w18V3#1646219 - Mar
将长度设置为0也会将位置设置为0,因此设置位置是多余的。 - Dmitrii Dovgopolyi
从安全角度来看,这是最佳答案。例如:当使用MemoryStream和CryptoStream时,您可以确保在完成后立即将内存清零,而不是等待垃圾收集器释放对象(如果它甚至会将字节清零)。 - Thoryn Hawley

1

我使用了 DotMemory 来对 @Rana_Ian 的解决方案进行性能分析,并调用 GC 强制进行全面的垃圾回收。我发现大型的 Streams 会被卡在 LOH 中!在添加了一行额外代码之后

public static void Clear(MemoryStream ms)
{
    var buffer = ms.GetBuffer();
    Array.Clear(buffer, 0, buffer.Length);
    ms.Position = 0;
    ms.SetLength(0);
    ms.Capacity = 0; // <<< this one ******
}

我点击了F12查看Capacity的实现,然后我发现了以下内容(我稍微简化了一下生成的代码 - 我正在使用Resharper):
public virtual int Capacity
{
  get
  { .... // some code }
  set
  {
    if ((long) value < this.Length) { // throw some ex }
    if (!this._isOpen) { // some another code }
    if (!this._expandable && value != this.Capacity) { //MemoryStreamNotExpandable }
    if (!this._expandable || value == this._capacity) return;
    if (value > 0)
    {
      byte[] numArray = new byte[value];
      if (this._length > 0)
        Buffer.InternalBlockCopy((Array) this._buffer, 0, (Array) numArray, 0, this._length);
      this._buffer = numArray;
    }
    else
      this._buffer = (byte[]) null; /// <<<< that's it! I need this one
    this._capacity = value;
  }
}

所以它正在清除缓冲区,我不确定是否正确!


-1
你可以像这样创建你的MemoryStream:
public class ResettableMemoryStream : MemoryStream
{
    public void Reset()
{
    Seek(0, SeekOrigin.Begin);
}
}  

在你的代码中进行操作后调用:
 buffer.Reset();

查看Seek函数的反编译代码,它只重置了Position,但没有重置Length。 - mBardos

-5

MemoryStream没有重置/清除方法,因为这将是多余的。通过将其设置为零长度,您可以清除它。

当然,您也可以执行以下操作:

memoryStream = new MemoryStream(memoryStream.Capacity());

这将为您产生一个已初始化的相同大小的内存流。

如果您真的想手动清除流,我怀疑您必须通过循环遍历元素来实现。


27
为什么要创建一个新的容量相同的流,而不是使用.SetLength(0)并保留容量?这是一种滥用内存的行为。 - CodeAngry
1
如果您在使用using语句时使用流,也无法正常工作。最好使用.SetLength(0)。 - Cadburry
1
这就是为什么我的答案指出将其设置为零长度会清除它...然后详细说明如何通过构造函数初始化具有相同长度的流。 - omglolbah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接