C#. Класс BinaryWriter. Работа с бинарными файлами




Класс BinaryWriter. Работа с бинарными файлами

Перед изучением данной темы рекомендуется ознакомиться со следующими темами:


Содержание


Поиск на других ресурсах:

1. Класс BinaryWriter. Назначение

Класс BinaryWriter предназначен для записи данных в двоичном (бинарном) формате. Запись данных может осуществляться в файлы, сеть, изолированное хранилище, память и т.п. Во время записи строк существует возможность указания нужной кодировки. По умолчанию установлена кодировка UTF-8.
Класс реализован в пространстве имен System.IO. Для того, чтобы использовать возможности этого класса нужно добавить строку

using System.IO;

Класс BinaryWriter записывает данные, которые представлены:

  • примитивными типами: int, float, double и другими;
  • строками типа string в указанной кодировке.

 

2. Взаимодействие класса BinaryWriter с потоками опорных хранилищ

Класс BinaryWriter (а также BinaryReader) относится к адаптерам потоков. Это означает следующее. Чтобы получить доступ к файлу, сети или памяти, нужно использовать промежуточный класс опорного хранилища (FileStream, MemoryStream, NetworkStream и т.д.).
На рисунке отображено взаимодействие класса BinaryWriter с файловым хранилищем, которому соответствует класс FileStream.

C#. Взаимодействие класса BinaryWriter с файлом через класс FileStream

Рисунок. Взаимодействие класса BinaryWriter с файлом через класс FileStream

 

3. Конструкторы класса BinaryWriter. Создание экземпляра

Класс BinaryWriter имеет несколько конструкторов, наиболее распространенными из которых следующие:

public BinaryWriter(Stream output)
public BinaryWriter(Stream output, System.Text.Encoding encoding)

здесь

  • output – ссылка на абстрактный класс Stream, являющийся вершиной иерархии классов ввода-вывода;
  • encoding – система кодировки (Unicode, UTF32, UTF8 или другая). По умолчанию установлена система кодировки UTF8.

Пример. Для того, чтобы создать экземпляр класса BinaryWriter и связать его с файлом «abc.bin» нужно выполнить приблизительно следующий код.

// Создать файл с именем "abc.bin" для записи
using (FileStream fs = new FileStream("abc.bin",
FileMode.Create, FileAccess.Write))
{
  // 2. Связать поток fs с экземпляром bw:
  // bw -> fs -> "abc.bin"
  using (BinaryWriter bw = new BinaryWriter(fs, Encoding.Default))
  {
    ...
  }

  ...

}

Как видно из примера, при создании потоков используется синтаксис с использованием ключевого слова using(). Это освобождает от необходимости закрывать потоки методом Close(), поскольку при таком подходе очистка ресурсов происходит автоматически.

 

4. Основные методы класса BinaryWriter
4.1. Метод Write() — много перегруженных реализаций

Главный метод класса BinaryWriter — метод Write(). Этот метод позволяет записывать в поток данные всех примитивных (стандартных) типов.

Метод имеет 20 перегруженных реализаций, основные из которых приведены ниже.

 

4.1.1. Запись одиночных значений. Перегруженный метод Write(). Пример

Чтобы записать одиночные значения используются методы Write(), которые имеют следующую общую форму

public virtual void Write(bool value)
public virtual void Write(byte value)
public virtual void Write(char ch)
public virtual void Write(decimal value)
public virtual void Write(double value)
public virtual void Write(float value)
public virtual void Write(int value)
public virtual void Write(long value)
public virtual void Write(sbyte value)
public virtual void Write(short value)
public virtual void Write(string value)
public virtual void Write(uint value)
public virtual void Write(ulong value)
public virtual void Write(ushort value)

здесь value – значение одного из стандартных (примитивных) типовв, которое нужно записать в поток.

Пример.

В примере в файл «data.bin» записываются данные разных типов с помощью метода Write(). Затем эти данные считываются с целью контроля.

using System;
using System.IO;
using System.Text;

namespace ConsoleApp10
{
  class Program
  {
    static void Main(string[] args)
    {
      // 1. Записать в файл "data.bin" данные разных типов
      using (FileStream fs = new FileStream("data.bin", FileMode.Create))
      {
        using (BinaryWriter bw = new BinaryWriter(fs, Encoding.Default))
        {
          // Запись данных разных типов в файл "data.bin"
          bool b = false; // тип bool
          int i = 233;
          double x = 2.338;
          decimal d = 0.011199m;
          float f = -30.88f;

          // Запись
          bw.Write(b); // записать тип bool
          bw.Write(i); // записать тип int
          bw.Write(x);
          bw.Write(d);
          bw.Write(f);
          bw.Write(1234567890123L); // записать число типа long
        }
      }

      // 2. Считывание разнотипных данных из файла "data.bin"
      using (FileStream fs = new FileStream("data.bin", FileMode.Open))
      {
        using (BinaryReader br = new BinaryReader(fs, Encoding.Default))
        {
          // Считывание данных разных типов
          bool b2 = br.ReadBoolean(); // считать тип bool
          int i2 = br.ReadInt32();
          double x2 = br.ReadDouble();
          decimal d2 = br.ReadDecimal();
          float f2 = br.ReadSingle();
          long l2 = br.ReadInt64();

          // Вывести прочитанные данные на экран для контроля
          Console.WriteLine("b2 = {0}", b2);
          Console.WriteLine("i2 = {0}", i2);
          Console.WriteLine("x2 = {0:f3}", x2); // точность 3 знака после запятой
          Console.WriteLine("d2 = {0}", d2);
          Console.WriteLine("f2 = {0:f2}", f2);
          Console.WriteLine($"l2 = {l2}"); // другой способ вывода на экран
        }
      }
    }
  }
}

Результат выполнения программы

b2 = False
i2 = 233
x2 = 2.338
d2 = 0.011199
f2 = -30.88
l2 = 1234567890123

 

4.1.2. Запись байтовых массивов byte[] в поток. Пример

Запись байт массивов типа byte[] полезен, когда в поток нужно записывать массивы стандартных типов (например, массивы int[], double[], decimal[] и т.д.). Для конвертирования из стандартных типов в тип byte[] удобно использовать возможности класса BitConverter.

public virtual void Write(byte[] buffer)
public virtual void Write(byte[] buffer, int index, int count)

здесь

  • buffer – участок памяти, из которого будут записываться данные в поток побайтно;
  • index – позиция (индекс) начала в массиве buffer, с которой будет происходить запись в поток;
  • count – количество байт, которые нужно записать в поток.

Пример. В примере, с помощью метода Write() в файл «array.dat» записывается массив чисел типа double[]. Затем этот массив считывается и выводится на экран для сверки.

using System;
using System.IO;
using System.Text;

namespace ConsoleApp10
{
  class Program
  {
    static void Main(string[] args)
    {
      // Записать в файл "array.bin" массив чисел типа double[]
      // 1. Исходный массив
      double[] AD = { 2.88, 3.11, 4.88 };

      // 2. Запись массива
      using (FileStream fs = new FileStream("array.bin", FileMode.Create))
      {
        using (BinaryWriter bw = new BinaryWriter(fs, Encoding.Default))
        {
          // Вспомогательный массив типа byte[]
          byte[] AB;

          // Сначала записать количество чисел в массиве AD
          bw.Write(AD.Length);

          // Цикл записи чисел decimal[]
          for (int i=0; i<AD.Length; i++)
          {
            // конвертировать byte[] <- double
            AB = BitConverter.GetBytes(AD[i]);

            // Записать double как массив byte[]
            bw.Write(AB, 0, AB.Length);

            // Так тоже можно
            //bw.Write(AB);
          }
        }
      }

      // 3. Считывание данных из файла "array.bin"
      using (FileStream fs = new FileStream("array.bin", FileMode.Open))
      {
        using (BinaryReader br = new BinaryReader(fs, Encoding.Default))
        {
          // Объявить дополнительные переменные
          double[] AD2; // результирующий массив

          // дополнительный массив-буфер
          byte[] B2 = new byte[sizeof(double)]; // выделить память для B2

          // Сначала считать количество чисел
          int count = br.ReadInt32();

          // Выделить память для массива AD2
          AD2 = new double[count];

          // Считать данные из файла как byte[] и конвертировать их в double
          for (int i = 0; i < AD2.Length; i++)
          {
            // Считать в буфер данные как byte[]
            br.Read(B2);

            // конвертировать byte[] -> double
            AD2[i] = BitConverter.ToDouble(B2);
          }

          // Вывести AD2 для контроля
          Console.Write("AD2 = { ");
          foreach (double x in AD2)
            Console.Write($"{x} ");
          Console.WriteLine(" }");
        }
      }
    }
  }
}

Результат выполнения программы

AD2 = { 2.88 3.11 4.88 }

 

4.1.3. Запись символьных массивов char[] в поток. Пример

Для записи строк символов в виде массива char[] используются две следующие реализации метода Write():

public virtual void Write(char[] chars)
public virtual void Write(char[] chars, int index, int count)

здесь

  • chars – массив символов, который нужно записать в поток;
  • index — позиция первого символа в массиве chars, начиная с которой нужно записать данные;
  • count — количество символов, которые нужно прочитать из массива chars и записать в поток.

Пример. В примере записывается строка типа string в файловый поток в виде символьного массива char[].

using System;
using System.IO;
using System.Text;

namespace ConsoleApp10
{
  class Program
  {
    static void Main(string[] args)
    {
      // Записать в файл "string.bin" строку в виде массива char[]
      // 1. Исходная строка
      string s = "bestprog.net";

      // 2. Запись строки
      using (FileStream fs = new FileStream("string.bin", FileMode.Create))
      {
        using (BinaryWriter bw = new BinaryWriter(fs, Encoding.Default))
        {
          // Вспомогательный массив типа char[]
          char[] chars;

          // Конвертировать string -> char[]
          chars = s.ToCharArray();

          // Записать количество символов в массиве, chars
          bw.Write(chars.Length);

          // Записать массив chars в файл
          bw.Write(chars);
        }
      }

      // 3. Считывание данных из файла "string.bin"
      using (FileStream fs = new FileStream("string.bin", FileMode.Open))
      {
        using (BinaryReader br = new BinaryReader(fs, Encoding.Default))
        {
          // Объявить вспомогательные переменныые
          string s2; // результирующая строка

          // дополнительный массив-буфер
          char[] chars2;

          // Считать количество элементов в файле
          int count = br.ReadInt32();

          // Считать строку как массив типа char[]
          chars2 = br.ReadChars(count);

          // Конвертировать строку chars2 в string
          s2 = new string(chars2);

          // Вывести s2 для контроля
          Console.WriteLine(s2); // bestprog.net
        }
      }
    }
  }
}

Результат выполнения программы

bestprog.net

 

4.2. Метод Seek(). Пример

Метод Seek() устанавливает позицию в текущем потоке. Общая форма метода следующая:

public virtual long Seek(int offset, System.IO.SeekOrigin origin)

здесь

  • offset — смещение в байтах относительно позиции origin;
  • origin — значение, указывающее начальную точку, с которой должна быть получена новая позиция.

Значение origin есть перечислением и имеет тип

enum System.IO.SeekOrigin

Значения из перечисления могут быть следующими:

  • SeekOrigin.Begin — начальная точка отсчета устанавливается на начало потока (файла);
  • SeekOrigin.End — начальная точка отсчета устанавливается в конец потока (файла);
  • SeekOrigin.Current — определяется текущая позиция в потоке (файле).

Пример. В примере продемонстрированы следующие операции:

  • запись чисел в файл;
  • перезапись ранее записанных чисел;
  • дописывание числа в конец файла.

 

using System;
using System.IO;
using System.Text;

namespace ConsoleApp10
{
  class Program
  {
    static void Main(string[] args)
    {
      // Запись в файл "data.bin" чисел и их замена
      // Создать поток, связанный с файлом data.bin: fs -> data.bin
      FileStream fw = new FileStream("data.bin", FileMode.Create);

      // Создать адаптер потока bw: bw -> fs -> data.bin
      BinaryWriter bw = new BinaryWriter(fw, Encoding.Default);

      // Записать число типа int
      bw.Write(255);

      // Записать число типа double
      bw.Write(7.88);

      // Перезаписать другое число типа double другим значением
      bw.Seek(sizeof(int), SeekOrigin.Begin); // смещение - размер типа int
      bw.Write(8.88);

      // Перезаписать первое число типа int другим значением
      bw.Seek(0, SeekOrigin.Begin); // смещение 0 относительно начала потока
      bw.Write(77);

      // Дописать в конец третье число типа float
      bw.Seek(0, SeekOrigin.End);
      bw.Write(100.05f);

      // Закрыть оба потока
      bw.Close();
      fw.Close();

      // 2. Проверка
      // Открыть файл "data.bin" для чтения: fr <- data.bin
      FileStream fr = new FileStream("data.bin", FileMode.Open);

      // Создать адаптер потока: br <- fr <- data.bin
      BinaryReader br = new BinaryReader(fr, Encoding.Default);

      // Считать целое число
      int d = br.ReadInt32();

      // Прочитать число типа double
      double x = br.ReadDouble();

      // Прочитать число типа float
      float f = br.ReadSingle();

      // Вывести результат
      Console.WriteLine($"d = {d}"); // d = 77
      Console.WriteLine($"x = {x}"); // x = 8.88
      Console.WriteLine($"f = {f}"); // f = 100.05

      // Закрыть оба потока
      bw.Close();
      fw.Close();
    }
  }
}

Результат выполнения программы

d = 77
x = 8.88
f = 100.05

 

4.3. Метод Flush(). Пример

Метод Flush() используется для очистки всех буферов текущего модуля записи. После этого можно записывать любые буферизированные данные на устройство, в которое на данный момент разрешено производить запись.

В наиболее общем случае вызов метода Flush() выглядит примерно так:

FileStream fs = new FileStream("file.dat", FileMode.Create);
BinaryWriter bw = new BinaryWriter(fs, Encoding.Default);

...

bw.Flush();

...
bw.Close();
fs.Close();

 


Связанные темы