Ответ 1
Если вы действительно хотите получить доступ к внутреннему значению _origin Value, вы можете использовать вызов MemoryStream.Seek(0, SeekOrigin.Begin). Возвращаемое значение будет точно значением _origin.
Я знал, что GetBuffer()
на MemoryStream в С#/.NET должен использоваться с осторожностью, потому что, поскольку документы описывают здесь, в конце могут быть неиспользуемые байты, поэтому вы должны обязательно смотреть только на первые байты 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)
)
Если вы действительно хотите получить доступ к внутреннему значению _origin Value, вы можете использовать вызов MemoryStream.Seek(0, SeekOrigin.Begin). Возвращаемое значение будет точно значением _origin.
Ответ в документе GetBuffer() MSDN, возможно, вы его пропустили.
Когда вы создаете MemoryStream
без предоставления байтового массива (byte[]
):
он создает расширяемую емкость, инициализированную до нуля.
Другими словами, MemoryStream будет ссылаться на byte[]
с надлежащим размером, когда будет сделан вызов Write
в потоке.
Таким образом, с помощью GetBuffer()
вы можете напрямую обращаться к базовому массиву и читать его.
Это может быть полезно, когда вы находитесь в ситуации, когда вы будете получать поток, не зная его размера. Если получаемый поток обычно очень большой, вызов GetBuffer()
будет намного быстрее, чем вызов ToArray()
, который копирует данные изнутри, см. ниже.
Чтобы получить только данные в буфере, используйте метод ToArray; однако ToArray создает копию данных в памяти.
Интересно, в какой момент вы могли вызвать GetBuffer() для получения ненужных данных в начале, это может быть между двумя вызовами Write
, когда данные из первого были бы собраны мусором, но я не уверен, что это могло случиться.
ToArray() является альтернативой GetBuffer(). Однако ToArray() делает копию объекта в памяти. Если байты больше 80000, объект будет помещен в кучу больших объектов (LOH). Пока ничего необычного. Однако GC не очень хорошо обрабатывает LOH и объекты в нем (память не освобождается, как вы ожидаете). Из-за этого может возникнуть исключение OutOfMemoryException. Решение состоит в том, чтобы либо вызвать GC.Collect(), чтобы эти объекты были собраны, либо использовать GetBuffer(), и создать несколько объектов меньшего размера (менее 80000 байт) - они не подойдут к LOH, и память будет освобождена, как ожидалось по GC.
Существует третий (лучший) вариант, который должен использовать только потоки, например. прочитайте все байты из MemoryStream и напрямую напишите их в HttpResponse.OutputStream(используя снова байтовый массив и 80000 байт в качестве буфера). Однако это не всегда возможно (как это было в моем случае).
В качестве резюме можно сказать, что, когда копия объекта в памяти не нужна, вам придется избегать ToArray(), и в тех случаях GetBuffer() может пригодиться, но может и не быть лучшим решением.
В .NET 4.6 появился новый API, bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer)
, который по духу похож на .GetBuffer()
. Этот метод возвращает ArraySegment
, который включает информацию _origin
, если это возможно.
См. этот вопрос для получения подробной информации о том, когда .TryGetBuffer()
вернет true и заполнит выходной параметр полезной информацией.
Это может быть полезно, если вы используете API низкого уровня, который принимает ArraySegment
, например Socket.Send. Вместо вызова ToArray
, который создаст другую копию массива, вы можете создать сегмент:
var segment=new ArraySegment<byte>(stream.GetBuffer(), 0, stream.Position);
а затем передать это методу Send
. Для больших данных это позволит избежать выделения нового массива и копирования в него, что может быть дорогостоящим.
GetBuffer()
всегда предполагает, что вы знаете структуру данных, подаваемых в строку (и ее использование). Если вы хотите получить данные из потока, вы всегда должны использовать один из предоставленных методов (например, ToArray()
).
Может быть что-то подобное, но только тот случай, о котором я мог думать сейчас, - это фиксированная структура или виртуальная файловая система, сидящая в потоке. Например, в вашей текущей позиции вы читаете смещение для файла, сидящего внутри потока. Затем вы создаете новый объект потока на основе этого буфера потока, но с другим _origin
. Это избавит вас от копирования всех данных для нового объекта, что может позволить вам сэкономить много памяти. Это избавит вас от переноса исходного буфера в качестве ссылки с вами, потому что вы всегда можете восстановить его еще раз.
Самый важный момент из GetBuffer
документации MSDN, за исключением того, что он не создает копию данных, заключается в том, что он возвращает массив с неиспользуемыми байтами:
Обратите внимание, что буфер содержит выделенные байты, которые могут быть неиспользуемы. Например, если строка "test" записывается в объект MemoryStream, длина буфера, возвращаемого из GetBuffer, равна 256, а не 4, а 252 байта не используется. Чтобы получить только данные в буфере, используйте метод ToArray; однако ToArray создает копию данных в памяти.
Итак, если вы действительно хотите избежать создания копии из-за ограничений памяти, вы должны быть осторожны, чтобы не отправлять весь массив из GetBuffer
по проводке или сбрасывать его в файл или вложение, поскольку этот буфер растет на полномочия 2 при каждом заполнении и почти всегда имеют много неиспользуемых байтов в конце.
GetBuffer()
чрезвычайно полезен, если вам нужно записать двоичные данные или двоичные файлы.
Например, скажите, что я читаю данные из БД & затем вы хотите записать часть этого в двоичные файлы, вы можете сначала записать данные в буфер при выполнении итерации & затем записать весь буфер в файл за один раз, избегая так много циклов ввода-вывода, чтобы не допустить запись данных непосредственно в файлы во время каждой итерации.
Позвольте мне привести вам пример в C#
:
String query = "Select Id, Name from table1";
SqlCommand cmd = new SqlCommand(query, con);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
MemoryStream memStream = new MemoryStream(10 * 1024 * 1024) // 10 Mb of membuffer
BinaryWriter memWtr = new BinaryWriter(memStream);
BinaryWriter wtrFile = new BinaryWriter(filePath);// file where you want to write your data eventually
Foreach (DataRow dr in dt.Rows)
{
memWtr.write((int)dr[0]);
memWtr.write(dr[0].ToString());
}
//now write whole buffer into File in single call
wtrFile.write(memStream.GetBuffer(), 0 , memStream.Position);
Надеюсь, это поможет.