41

我知道GetBuffer()在 C#/.NET 中的 MemoryStream 上必须小心使用,因为正如这里的文档所述,最后可能有未使用的字节,所以你必须确保只查看第一个 MemoryStream .Length 缓冲区中的字节。

但是昨天我遇到了一个情况,缓冲区开头的字节是垃圾!实际上,如果您使用反射器之类的工具并查看ToArray(),您可以看到:

public virtual byte[] ToArray()
{
    byte[] dst = new byte[this._length - this._origin];
    Buffer.InternalBlockCopy(this._buffer, this._origin, dst, 0,
        this._length - this._origin);
    return dst;
}

所以要对返回的缓冲区做任何事情GetBuffer(),你真的需要知道_origin。唯一的问题是 _origin 是私有的,没有办法得到它......

所以我的问题是 - 如果没有GetBuffer()关于MemoryStream()MemoryStream 是如何构造的先验知识(这是什么设置 _origin),那么它有什么用?

(正是这个构造函数,并且只有这个构造函数,才能设置原点——因为当你想要一个围绕字节数组的 MemoryStream 时,该字节数组从字节数组中的特定索引开始:

public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)

)

4

7 回答 7

21

答案在GetBuffer() MSDN doc中,您可能错过了它。

当您在MemoryStream不提供字节数组 ( byte[]) 的情况下创建:

它创建了一个初始化为零的可扩展容量。

换句话说,当对 Stream 进行调用byte[]时,MemoryStream 将引用具有适当大小的 a 。Write

因此,GetBuffer()您可以直接访问底层数组并读取它。

当您在不知道其大小的情况下接收流时,这可能很有用。如果接收到的流通常非常大,那么调用它会GetBuffer()比调用ToArray()which copy the data under the hood 要快得多,见下文。

要仅获取缓冲区中的数据,请使用 ToArray 方法;但是,ToArray 会在内存中创建数据的副本。

我想知道你可能在什么时候调用 GetBuffer() 来获取垃圾数据,它可能在两个Write调用之间,第一个调用的数据将被垃圾收集,但我不确定这是否会发生。

于 2013-05-16T21:44:22.107 回答
17

如果你真的想访问内部的 _origin 值,你可以使用 MemoryStream.Seek(0, SeekOrigin.Begin) 调用。返回值将完全是 _origin 值。

于 2015-05-06T23:27:01.003 回答
14

.NET 4.6 有一个新的 API,bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer)在精神上与.GetBuffer(). 如果可以,此方法将返回一个ArraySegment包含_origin信息的内容。

有关何时返回 true 并使用有用信息填充 out 参数的详细信息,请参阅此问题。.TryGetBuffer()

于 2015-06-12T16:50:46.243 回答
12

ToArray() 是 GetBuffer() 的替代方法。但是 ToArray() 会在内存中复制对象。如果字节数超过 80000,则对象将被放置在大对象堆 (LOH) 中。到目前为止,没有什么花哨的。但是,GC 不能很好地处理 LOH 和其中的对象(内存没有像您期望的那样释放)。因此可能会发生 OutOfMemoryException。解决方案是调用 GC.Collect() 以便收集这些对象或使用 GetBuffer() 并创建几个更小的(小于 80000 字节)对象 - 这些对象不会进入 LOH,内存将按预期释放由 GC。

存在第三个(更好的)选项,即仅使用流,例如从 MemoryStream 读取所有字节并将它们直接写入 HttpResponse.OutputStream(再次使用 < 80000 字节的字节数组作为缓冲区)。然而,这并不总是可能的(就像我的情况一样)。

作为总结,我们可以说,当不需要对象的内存副本时,您将不得不避免使用 ToArray(),在这种情况下 GetBuffer() 可能会派上用场,但可能不是最佳解决方案。

于 2012-11-20T16:16:11.797 回答
9

如果您使用的是采用 Socket.Send 的低级 API,它会ArraySegment很有。您可以创建一个段,而不是调用ToArraywhich 将创建数组的另一个副本:

var segment=new ArraySegment<byte>(stream.GetBuffer(), 0, stream.Position);

然后将其传递给Send方法。对于大数据,这将避免分配新数组并将其复制到其中,这可能会很昂贵。

于 2015-05-01T13:41:27.383 回答
5

GetBuffer MSDN 文档中最重要的一点是,除了它创建数据副本之外,它返回一个包含未使用字节的数组:

请注意,缓冲区包含可能未使用的已分配字节。例如,如果将字符串“test”写入 MemoryStream 对象,则从 GetBuffer 返回的缓冲区长度为 256,而不是 4,其中 252 个字节未使用。要仅获取缓冲区中的数据,请使用 ToArray 方法;但是,ToArray 会在内存中创建数据的副本。

因此,如果您真的想避免由于内存限制而创建副本,则必须小心不要GetBuffer通过线路发送整个数组或将其转储到文件或附件中,因为该缓冲区在任何时候都会增长 2 的幂填充并且几乎总是在最后有很多未使用的字节。

于 2016-11-01T10:27:33.023 回答
4

GetBuffer()总是假设您知道输入字符串的数据的结构(这就是它的用途)。如果您想从流中获取数据,您应该始终使用提供的方法之一(例如ToArray())。

可以使用这样的东西,但我现在能想到的唯一情况是流中的一些固定结构或虚拟文件系统。例如,在您当前的位置,您正在读取位于流中的文件的偏移量。然后,您基于此流的缓冲区创建一个新的流对象,但使用不同的_origin. 这样可以避免复制新对象的全部数据,从而节省大量内存。这使您不必携带初始缓冲区作为参考,因为您总是能够再次检索它。

于 2012-10-24T16:55:52.370 回答