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();

 


Зв’язані теми