Использование 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, но многие утверждают, что маршаллинг должен быть у вас на уме. (Я просто хотел представить что-то, чтобы задуматься...)