Для работы с бинарными файлами предназначена пара классов BinaryWriter и BinaryReader. Эти классы позволяют читать и записывать данные в двоичном формате.
Для создания объекта BinaryWriter можно применять ряд конструкторов. Возьмем наиболее простую:
BinaryWriter(Stream stream)
в его конструктор передается объект Stream (обычно это объект FileStream).
Основные методы класса BinaryWriter
Close(): закрывает поток и освобождает ресурсы
Flush(): очищает буфер, дописывая из него оставшиеся данные в файл
Seek(): устанавливает позицию в потоке
Write(): записывает данные в поток. В качестве параметра этот метод может принимать значения примитивных данных:
Write(bool)
Write(byte)
Write(char)
Write(decimal)
Write(double)
Write(Half)
Write(short)
Write(int)
Write(long)
Write(sbyte)
Write(float)
Write(string)
Write(ushort)
Write(uint)
Write(ulong)
Либо можно передать массивы типов byte и char
Write(byte[])
Write(char[])
Write(ReadOnlySpan<byte>)
Write(ReadOnlySpan<char>)
При записи массива дополнительно можно указать, с кого элемента массива надо выполнять запись, а также число записываемых элементов массива:
Write(byte[], int, int)
Write(char[], int, int)
Рассмотрим простейшую запись бинарного файла:
string path = "person.dat"; // создаем объект BinaryWriter using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.OpenOrCreate))) { // записываем в файл строку writer.Write("Tom"); // записываем в файл число int writer.Write(37); Console.WriteLine("File has been written"); }
Здесь в файл person.dat записываются два значения: строка "Tom" и число 37. Для создание объекта применяется вызов
new BinaryWriter(File.Open(path, FileMode.OpenOrCreate))
Подобным образом можно сохранять более сложные данные. Например, сохраним в файл массив объектов:
string path = "people.dat"; // массив для записи Person[] people = { new Person("Tom", 37), new Person("Bob", 41) }; using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.OpenOrCreate))) { // записываем в файл значение каждого свойства объекта foreach (Person person in people) { writer.Write(person.Name); writer.Write(person.Age); } Console.WriteLine("File has been written"); } class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } }
В данном случае последовательно сохраняем в файл people.dat данные объектов Person из массива people.
Для создания объекта BinaryReader можно применять ряд конструкторов. Возьмем наиболее простую версию:
Reader(Stream stream)
в его конструктор также передается объект Stream (также обычно это объект FileStream).
Основные методы класса BinaryReader
Close(): закрывает поток и освобождает ресурсы
ReadBoolean(): считывает значение bool и перемещает указатель на один байт
ReadByte(): считывает один байт и перемещает указатель на один байт
ReadChar(): считывает значение char, то есть один символ, и перемещает указатель на столько байтов, сколько занимает символ в текущей кодировке
ReadDecimal(): считывает значение decimal и перемещает указатель на 16 байт
ReadDouble(): считывает значение double и перемещает указатель на 8 байт
ReadInt16(): считывает значение short и перемещает указатель на 2 байта
ReadInt32(): считывает значение int и перемещает указатель на 4 байта
ReadInt64(): считывает значение long и перемещает указатель на 8 байт
ReadSingle(): считывает значение float и перемещает указатель на 4 байта
ReadString(): считывает значение string. Каждая строка предваряется значением длины строки, которое представляет 7-битное целое число
С чтением бинарных данных все просто: соответствующий метод считывает данные определенного типа и перемещает указатель на размер этого типа в байтах, например, значение типа int занимает 4 байта, поэтому BinaryReader считает 4 байта и переместит указатель на эти 4 байта.
Например, выше в примере с BinaryWriter в файл person.dat записывалась строка и число. Считаем их с помощью BinaryReader:
using (BinaryReader reader = new BinaryReader(File.Open("person.dat", FileMode.Open))) { // считываем из файла строку string name = reader.ReadString(); // считываем из файла число int age = reader.ReadInt32(); Console.WriteLine($"Name: {name} Age: {age}"); }
Конструктор класса BinaryReader также в качестве параметра принимает объект потока, только в данном
случае устанавливаем в качестве режима FileMode.Open: new BinaryReader(File.Open("person.dat", FileMode.Open))
.
В каком порядке данные были записаны в файл, в таком порядке мы их можем оттуда считать. То есть если сначала записывалась строка, а потом число, то в данном порядке мы их можем считать из файла.
Или подобным образом считаем данные из файла people.dat, который был записан в примере выше и который содержит данные объектов Person:
// список для считываемых данных List<Person> people = new List<Person>(); // создаем объект BinaryWriter using (BinaryReader reader = new BinaryReader(File.Open("people.dat", FileMode.Open))) { // пока не достигнут конец файла // считываем каждое значение из файла while (reader.PeekChar() > -1) { string name = reader.ReadString(); int age = reader.ReadInt32(); // по считанным данным создаем объект Person и добавляем в список people.Add(new Person(name, age)); } } // выводим содержимое списка people на консоль foreach(Person person in people) { Console.WriteLine($"Name: {person.Name} Age: {person.Age}"); } class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } }
Здесь в цикле while
считываем данные. Чтобы узнать окончание потока, вызываем метод PeekChar()
. Этот метод считывает
следующий символ и возвращает его числовое представление. Если символ отсутствует, то метод возвращает -1, что будет означать, что мы достигли конца файла.
В цикле последовательно считываем значения для свойств объектов Person в том же порядке, в каком они записывались.