Использование Stream.Read() vs BinaryReader.Read() для обработки двоичных потоков

При работе с бинарными потоками (т.е. byte[] массивы) основной смысл использования BinaryReader или BinaryWriter представляется упрощенным чтением/записью примитивных типов данных из потока с использованием таких методов, как ReadBoolean() и принимая во внимание кодировку. Это целая история? Есть ли неотъемлемое преимущество или недостаток, если вы работаете напрямую с Stream, не используя BinaryReader/BinaryWriter? Большинство методов, таких как Read(), кажутся одинаковыми в обоих классах, и я предполагаю, что они работают одинаково под ним.

Рассмотрим простой пример обработки двоичного файла двумя разными способами (редактирование: я понимаю, что этот способ неэффективен и что можно использовать буфер, это просто образец):

// Using FileStream directly
using (FileStream stream = new FileStream("file.dat", FileMode.Open))
{
    // Read bytes from stream and interpret them as ints
    int value = 0;
    while ((value = stream.ReadByte()) != -1)
    {
        Console.WriteLine(value);
    }
}


// Using BinaryReader
using (BinaryReader reader = new BinaryReader(FileStream fs = new FileStream("file.dat", FileMode.Open)))
{
    // Read bytes and interpret them as ints
    byte value = 0;    
    while (reader.BaseStream.Position < reader.BaseStream.Length)
    {
        value = reader.ReadByte();
        Console.WriteLine(Convert.ToInt32(value));
    }
}

Результат будет таким же, но что происходит внутри (например, с точки зрения ОС)? Является ли, вообще говоря, важным, какая реализация используется? Есть ли смысл использовать BinaryReader/BinaryWriter, если вам не нужны дополнительные методы, которые они предоставляют? В этом конкретном случае MSDN говорит об этом в отношении Stream.ReadByte():

Реализация по умолчанию для Stream создает новый однобайтовый массив а затем вызывает чтение. Хотя это формально правильно, неэффективен.

Используя GC.GetTotalMemory(), этот первый подход, как представляется, выделяет 2x столько же места, сколько и второе, но AFAIK это не должно быть, если используется более общий метод Stream.Read() (например, для чтения в кусках, используя буфер). Тем не менее, мне кажется, что эти методы/интерфейсы могут быть легко унифицированы...

Ответы

Ответ 1

Нет, принципиальной разницы между этими двумя подходами нет. Дополнительный Reader добавляет некоторую буферизацию, поэтому вы не должны смешивать их. Но не ожидайте каких-либо существенных различий в производительности, в них все доминируют фактические операции ввода-вывода.

Итак,

  • используйте поток, если у вас есть (только) byte[] для перемещения. Как это принято во многих сценариях потоковой передачи.
  • используйте BinaryWriter и BinaryReader, если у вас есть какой-либо другой базовый тип (включая простой byte) данных для обработки. Их основная цель - преобразование встроенных типов фреймов в byte[].

Ответ 2

Одна большая разница заключается в том, как вы можете буферизовать ввод-вывод. Если вы пишете или читаете только несколько байтов здесь или там, BinaryWriter/BinaryReader будет работать хорошо. Но если вам нужно читать MBs данных, то чтение одного byte, Int32 и т.д. В то время будет немного медленным. Вместо этого вы могли читать большие куски и сидеть там.

Пример:

// Using FileStream directly with a buffer
using (FileStream stream = new FileStream("file.dat", FileMode.Open))
{
    // Read bytes from stream and interpret them as ints
    byte[] buffer = new byte[1024];
    int count;
    // Read from the IO stream fewer times.
    while((count = stream.Read(buffer, 0, buffer.Length)) > 0)
        for(int i=0; i<count; i++)
           Console.WriteLine(Convert.ToInt32(buffer[i]));
}

Теперь это немного не по теме... но я выброшу его там: Если вы хотите получить ОЧЕНЬ хитрый... и действительно дайте себе повышение производительности... (хотя, это может считаться опасным) Вместо разбора EACH Int32 вы можете сделать все сразу, используя Buffer.BlockCopy()

Другой пример:

// Using FileStream directly with a buffer and BlockCopy
using (FileStream stream = new FileStream("file.dat", FileMode.Open))
{
    // Read bytes from stream and interpret them as ints
    byte[] buffer = new byte[1024];
    int[] intArray = new int[buffer.Length >> 2]; // Each int is 4 bytes
    int count;
    // Read from the IO stream fewer times.
    while((count = stream.Read(buffer, 0, buffer.Length)) > 0)
    {
       // Copy the bytes into the memory space of the Int32 array in one big swoop
       Buffer.BlockCopy(buffer, 0, intArray, count);

       for(int i=0; i<count; i+=4)
          Console.WriteLine(intArray[i]);
    }
}

Несколько вещей, которые следует отметить в этом примере: это занимает 4 байта на Int32 вместо одного... Таким образом, он даст разные результаты. Вы также можете сделать это для других типов данных, отличных от Int32, но многие утверждают, что маршаллинг должен быть у вас на уме. (Я просто хотел представить что-то, чтобы задуматься...)