Когда GetBuffer () в MemoryStream может быть полезен?


Я знал, что GetBuffer() на потоке памяти в C#/.NET должен использоваться с осторожностью, потому что, как описывают документы здесь, в конце могут быть неиспользуемые байты, поэтому вы должны быть уверены, что смотрите только на первый поток памяти.Длина байт в буфере.

Но вчера я столкнулся со случаем, когда байты в начале буфера были мусором! Действительно, если вы используете такой инструмент, как рефлектор, и посмотрите на 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. Единственная проблема в том, что оригин-частное лицо, и до него никак не добраться...

Итак, мой вопрос - какая польза GetBuffer() на MemoryStream() без некоторого априорного знания о том, как был построен поток памяти (который является тем, что задает _origin)?

(именно этот конструктор, и только этот конструктор, задает origin-for, когда вы хотите, чтобы поток памяти вокруг массива байтов начинался с определенного индекса в байте массив:

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

)

7 25

7 ответов:

Если вы действительно хотите получить доступ к внутреннему значению _origin, вы можете использовать MemoryStream.Seek(0, SeekOrigin.Начинайте) звонить. Возвращаемое значение будет точно таким же, как значение _origin.

Ответ находится в GetBuffer() MSDN doc , возможно, вы его пропустили.

При создании MemoryStream без предоставления массива байтов (byte[]) :

Это создает возможность расширения емкости обнуляются.

Другими словами, поток памяти будет ссылаться на byte[] с соответствующим размером, когда вызов Write будет выполнен в потоке.

Таким образом, с помощью GetBuffer() Вы можете напрямую получить доступ к базовому массиву и прочитать его.

Это может быть полезно, когда вы находитесь в ситуации, когда вы получите поток, не зная его размера. Если полученный поток обычно очень большой, то вызов GetBuffer() будет намного быстрее, чем вызов ToArray(), которые копируют данные под капотом, см. ниже.

Чтобы получить только данные в буфере, используйте метод ToArray; однако ToArray создает копию данных в памяти.

интересно, в какой момент Вы могли бы позвонить GetBuffer (), чтобы получить мусор данные в начале, это может быть между двумя вызовами Write, где данные из первого были бы собраны мусор, но я не уверен, что это может произойти.

ToArray () является альтернативой GetBuffer (). Однако ToArray() создает копию объекта в памяти. Если байт больше 80000, объект будет помещен в большую кучу объектов (LOH). Пока ничего особенного. Однако GC не очень хорошо обрабатывает LOH и объекты в нем (память не освобождается, как вы ожидаете). Из-за этого может возникнуть исключение OutOfMemoryException. Решение состоит в том, чтобы либо вызвать GC.Собрать () так, чтобы эти объекты были собраны или использовать GetBuffer () и создать несколько меньших (менее 80000 байт) объектов-те не пойдут в LOH, и память будет освобождена, как и ожидалось GC.

Существует третий (лучший) вариант, который заключается в использовании только потоков, например, считывании всех байтов из потока памяти и прямой записи их в HttpResponse.OutputStream (снова используя байтовый массив

В качестве резюме мы можем сказать, что когда копия объекта в памяти не является при желании вам придется избегать ToArray (), и в этих случаях GetBuffer () может пригодиться, но не может быть лучшим решением.

Это может быть полезно, если вы используете низкоуровневый API, который принимает сокет ArraySegment, например .Отправить . Вместо вызова ToArray, который создаст еще одну копию массива, вы можете создать сегмент:

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

И затем передайте это в метод Send. Для больших данных это позволит избежать выделения нового массива и копирования в него, что может быть дорого.

GetBuffer() всегда предполагается, что вы знаете структуру данных, подаваемых в строку (и это ее использование). Если вы хотите получить данные из потока, вы всегда должны использовать один из предоставленных методов (например, ToArray()).

Что-то вроде этого можно использовать, но единственный случай, который я мог бы сейчас придумать, - это какая-то фиксированная структура или виртуальная файловая система, находящаяся в потоке. Например, в текущей позиции вы считываете смещение для файла, находящегося внутри потока. Затем вы создаете новый объект потока, основанный на буфере этого потока, но с другим _origin. Это избавляет вас от копирования всех данных для нового объекта, что может позволить вам сэкономить много памяти. Это избавляет вас от необходимости носить исходный буфер в качестве ссылки с собой, потому что вы всегда можете получить его еще раз.

В .NET 4.6 появился новый API, bool MemoryStream.TryGetBuffer(out ArraySegment<byte> buffer) это похоже по духу на .GetBuffer(). Этот метод вернет значение ArraySegment это включает в себя информацию _origin, Если это возможно.

Смотритеэтот вопрос для получения подробной информации о том, когда .TryGetBuffer() вернет true и заполнит out param полезной информацией.

Самый важный момент из GetBuffer документация MSDN , Кроме того, что она Не создает копию данных, заключается в том, что она возвращает массив, который имеет неиспользуемые байты:

Обратите внимание, что буфер содержит выделенные байты, которые могут быть неиспользуемыми. Например, если строка "test" записана в объект MemoryStream, длина буфера, возвращаемого из GetBuffer, составляет 256, а не 4, при этом 252 байта не используются. Чтобы получить только данные в буфере, используйте Метод ToArray; однако ToArray создает копию данных в памяти.
Таким образом, если вы действительно хотите избежать создания копии из-за ограничений памяти, вы должны быть осторожны, чтобы не отправить весь массив из GetBufferпо проводу или не сбросить его в файл или вложение, потому что этот буфер растет на степени 2 всякий раз, когда он заполнен и почти всегда имеет много неиспользуемых байтов в конце.