.net - ¿Cuándo es útil GetBuffer() en MemoryStream?
(7)
.NET 4.6 tiene una nueva API, bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer)
que es similar en espíritu al .GetBuffer()
. Este método devolverá un ArraySegment
que incluye la información de origen si puede.
Consulte esta pregunta para obtener detalles sobre cuándo. .TryGetBuffer()
devolverá verdadero y .TryGetBuffer()
out con información útil.
GetBuffer()
que GetBuffer()
en un MemoryStream en C # / .NET debe usarse con cuidado, porque, como describen los documentos here , puede haber bytes no utilizados al final, por lo que debe asegurarse de mirar solo Los primeros bytes de MemoryStream.Length en el búfer.
¡Pero luego me encontré con un caso ayer donde los bytes al principio del búfer eran basura! De hecho, si usa una herramienta como el reflector y mira ToArray()
, puede ver esto:
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;
}
Entonces, para hacer cualquier cosa con el búfer devuelto por GetBuffer()
, realmente necesitas saber _origin. El único problema es que _origin es privado y no hay manera de llegar a eso ...
Entonces, mi pregunta es: ¿de qué sirve GetBuffer()
en un MemoryStream()
sin un conocimiento previo de cómo se construyó el MemoryStream (que es lo que establece a _origin)?
(Es este constructor, y solo este constructor, el que establece el origen, para cuando quiere un MemoryStream alrededor de una matriz de bytes que comienza en un índice particular en la matriz de bytes:
public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
)
El punto más importante de la documentación de GetBuffer
MSDN , aparte de no crear una copia de los datos, es que devuelve una matriz que tiene bytes no utilizados :
Tenga en cuenta que el búfer contiene bytes asignados que no se pueden utilizar. Por ejemplo, si la cadena "prueba" se escribe en el objeto MemoryStream, la longitud del búfer devuelto por GetBuffer es 256, no 4, con 252 bytes sin usar. Para obtener solo los datos en el búfer, use el método ToArray; Sin embargo, ToArray crea una copia de los datos en la memoria.
Entonces, si realmente desea evitar crear una copia debido a restricciones de memoria, debe tener cuidado de no enviar toda la matriz de GetBuffer
por cable o descargarla en un archivo o archivo adjunto, porque ese búfer crece en potencias de 2 cada vez que está lleno y casi siempre tiene muchos bytes no utilizados al final.
La respuesta está en el here , es posible que lo haya perdido.
Cuando crea un MemoryStream
sin proporcionar una matriz de byte[]
( byte[]
):
crea una capacidad expandible inicializada a cero.
En otras palabras, el MemoryStream hará referencia a un byte[]
con el tamaño adecuado cuando se realizará una llamada de Write
en el Stream.
Por lo tanto, con GetBuffer()
puede acceder directamente a la matriz subyacente y leer en ella.
Esto podría ser útil cuando estás en la situación de que recibirás un flujo sin saber su tamaño. Si la transmisión recibida suele ser muy grande, será mucho más rápido llamar a GetBuffer()
que a ToArray()
que copia los datos debajo del capó, consulte a continuación.
Para obtener solo los datos en el búfer, use el método ToArray; Sin embargo, ToArray crea una copia de los datos en la memoria.
Me pregunto en qué momento podría haber llamado a GetBuffer () para obtener datos no deseados al principio, podría ser entre dos llamadas de Write
en las que los datos de la primera hubieran sido recolectados, pero no estoy seguro si eso podría suceder. .
Puede ser útil si está utilizando una API de bajo nivel que toma un ArraySegment
, como Socket.Send . En lugar de llamar a ToArray
que creará otra copia de la matriz, puede crear un segmento:
var segment=new ArraySegment<byte>(stream.GetBuffer(), 0, stream.Position);
y luego pasar eso al método de Send
. Para datos grandes, esto evitará asignar una nueva matriz y copiarla, lo que podría ser costoso.
Si realmente desea acceder al Valor de origen interno, puede usar la llamada MemoryStream.Seek (0, SeekOrigin.Begin). El Valor de retorno será exactamente el Valor de origen.
ToArray () es la alternativa de GetBuffer (). Sin embargo, ToArray () hace una copia del objeto en la memoria. Si los bytes son más de 80000, el objeto se colocará en el montón de objetos grandes (LOH). Hasta ahora nada de lujos. Sin embargo, el GC no maneja muy bien el LOH y los objetos que contiene (la memoria no se libera como se espera). Debido a esto, puede producirse la excepción OutOfMemoryException. La solución es llamar a GC.Collect () para que esos objetos se recopilen o usar GetBuffer () y crear varios objetos más pequeños (menos de 80000 bytes); no irán a la LOH y la memoria se liberará como se espera. por el cc.
Existe una tercera opción (mejor) y es usar solo flujos, p. Ej., Leer todos los bytes de un MemoryStream y escribirlos directamente en HttpResponse.OutputStream (usando nuevamente la matriz de bytes <80000 bytes como búfer). Sin embargo, esto no siempre es posible (como lo fue en mi caso).
Como resumen, podemos decir que cuando no se desea una copia en memoria del objeto, tendrá que evitar ToArray () y en esos casos, GetBuffer () podría ser útil, pero podría no ser la mejor solución.
GetBuffer()
siempre asume que conoce la estructura de los datos introducidos en la cadena (y ese es su uso). Si desea obtener datos de la transmisión, siempre debe utilizar uno de los métodos proporcionados (por ejemplo, ToArray()
).
Se puede usar algo como esto, pero el único caso que se me ocurre ahora sería una estructura fija o un sistema de archivos virtual en la corriente. Por ejemplo, en su posición actual está leyendo una compensación para un archivo que se encuentra dentro de la secuencia. A continuación, crea un nuevo objeto de secuencia basado en el búfer de esta secuencia pero con el diferente _origin
. Esto evita que copie todos los datos del nuevo objeto, lo que podría permitirle ahorrar mucha memoria. Esto evita que lleve consigo el búfer inicial como referencia, porque siempre puede recuperarlo una vez más.