Как создать двоичный файл в c
Для работы с бинарными файлами предназначена пара классов BinaryWriter и BinaryReader . Эти классы позволяют читать и записывать данные в двоичном формате.
BinaryWriter
Для создания объекта BinaryWriter можно применять ряд конструкторов. Возьмем наиболее простую:
в его конструктор передается объект Stream (обычно это объект FileStream).
Основные методы класса BinaryWriter
Close() : закрывает поток и освобождает ресурсы
Flush() : очищает буфер, дописывая из него оставшиеся данные в файл
Seek() : устанавливает позицию в потоке
Write() : записывает данные в поток. В качестве параметра этот метод может принимать значения примитивных данных:
Либо можно передать массивы типов byte и char
При записи массива дополнительно можно указать, с кого элемента массива надо выполнять запись, а также число записываемых элементов массива:
Write(byte[], int, int)
Write(char[], int, int)
Рассмотрим простейшую запись бинарного файла:
Здесь в файл person.dat записываются два значения: строка «Tom» и число 37. Для создание объекта применяется вызов new BinaryWriter(File.Open(path, FileMode.OpenOrCreate))
Подобным образом можно сохранять более сложные данные. Например, сохраним в файл массив объектов:
В данном случае последовательно сохраняем в файл people.dat данные объектов Person из массива people.
BinaryReader
Для создания объекта BinaryReader можно применять ряд конструкторов. Возьмем наиболее простую версию:
в его конструктор также передается объект 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:
Конструктор класса BinaryReader также в качестве параметра принимает объект потока, только в данном случае устанавливаем в качестве режима FileMode.Open: new BinaryReader(File.Open(«person.dat», FileMode.Open)) .
В каком порядке данные были записаны в файл, в таком порядке мы их можем оттуда считать. То есть если сначала записывалась строка, а потом число, то в данном порядке мы их можем считать из файла.
Или подобным образом считаем данные из файла people.dat, который был записан в примере выше и который содержит данные объектов Person:
Здесь в цикле while считываем данные. Чтобы узнать окончание потока, вызываем метод PeekChar() . Этот метод считывает следующий символ и возвращает его числовое представление. Если символ отсутствует, то метод возвращает -1, что будет означать, что мы достигли конца файла.
В цикле последовательно считываем значения для свойств объектов Person в том же порядке, в каком они записывались.
Бинарные файлы
Т екстовые файлы хранят данные в виде текста (sic!). Это значит, что если, например, мы записываем целое число 12345678 в файл, то записывается 8 символов, а это 8 байт данных, несмотря на то, что число помещается в целый тип. Кроме того, вывод и ввод данных является форматированным, то есть каждый раз, когда мы считываем число из файла или записываем в файл происходит трансформация числа в строку или обратно. Это затратные операции, которых можно избежать.
Текстовые файлы позволяют хранить информацию в виде, понятном для человека. Можно, однако, хранить данные непосредственно в бинарном виде. Для этих целей используются бинарные файлы.
Выполните программу и посмотрите содержимое файла output.bin. Число, которое ввёл пользователь записывается в файл непосредственно в бинарном виде. Можете открыть файл в любом редакторе, поддерживающем представление в шестнадцатеричном виде (Total Commander, Far) и убедиться в этом.
Запись в файл осуществляется с помощью функции
Функция возвращает число удачно записанных элементов. В качестве аргументов принимает указатель на массив, размер одного элемента, число элементов и указатель на файловый поток. Вместо массив, конечно, может быть передан любой объект.
Запись в бинарный файл объекта похожа на его отображение: берутся данные из оперативной памяти и пишутся как есть. Для считывания используется функция fread
Функция возвращает число удачно прочитанных элементов, которые помещаются по адресу ptr. Всего считывается count элементов по size байт. Давайте теперь считаем наше число обратно в переменную.
fseek
Одной из важных функций для работы с бинарными файлами является функция fseek
Эта функция устанавливает указатель позиции, ассоциированный с потоком, на новое положение. Индикатор позиции указывает, на каком месте в файле мы остановились. Когда мы открываем файл, позиция равна 0. Каждый раз, записывая байт данных, указатель позиции сдвигается на единицу вперёд.
fseek принимает в качестве аргументов указатель на поток и сдвиг в offset байт относительно origin. origin может принимать три значения
- SEEK_SET — начало файла
- SEEK_CUR — текущее положение файла
- SEEK_END — конец файла. К сожалению, стандартом не определено, что такое конец файла, поэтому полагаться на эту функцию нельзя.
В случае удачной работы функция возвращает 0.
Дополним наш старый пример: запишем число, затем сдвинемся указатель на начало файла и прочитаем его.
Вместо этого можно также использовать функцию rewind, которая перемещает индикатор позиции в начало.
В си определён специальный тип fpos_t, который используется для хранения позиции индикатора позиции в файле.
Функция
используется для того, чтобы назначить переменной pos текущее положение. Функция
используется для перевода указателя в позицию, которая хранится в переменной pos. Обе функции в случае удачного завершения возвращают ноль.
возвращает текущее положение индикатора относительно начала файла. Для бинарных файлов — это число байт, для текстовых не определено (если текстовый файл состоит из однобайтовых символов, то также число байт).
Рассмотрим пример: пользователь вводит числа. Первые 4 байта файла: целое, которое обозначает, сколько чисел было введено. После того, как пользователь прекращает вводить числа, мы перемещаемся в начало файла и записываем туда число введённых элементов.
Вторая программа сначала считывает количество записанных чисел, а потом считывает и выводит числа по порядку.
Примеры
1. Имеется бинарный файл размером 10*sizeof(int) байт. Пользователь вводит номер ячейки, после чего в неё записывает число. После каждой операции выводятся все числа. Сначала пытаемся открыть файл в режиме чтения и записи. Если это не удаётся, то пробуем создать файл, если удаётся создать файл, то повторяем попытку открыть файл для чтения и записи.
2. Пишем слова в бинарный файл. Формат такой — сначало число букв, потом само слово без нулевого символа. Ели длина слова равна нулю, то больше слов нет. Сначала запрашиваем слова у пользователя, потом считываем обратно.
3. Задача — считать данные из текстового файла и записать их в бинарный. Для решения зачи создадим функцию обёртку. Она будет принимать имя файла, режим доступа, функцию, которую необходимо выполнить, если файл был удачно открыт и аргументы этой функции. Так как аргументов может быть много и они могут быть разного типа, то их можно передавать в качестве указателя на структуру. После выполнения функции файл закрывается. Таким образом, нет необходимости думать об освобождении ресурсов.
4. Функция saveInt32Array позволяет сохранить массив типа int32_t в файл. Обратная ей loadInt32Array считывает массив обратно. Функция loadInt32Array сначала инициализирует переданный ей массив, поэтому мы должны передавать указатель на указатель; кроме того, она записывает считанный размер массива в переданный параметр size, из-за чего он передаётся как указатель.
5. Создание таблицы поиска. Для ускорения работы программы вместо вычисления функции можно произвести сначала вычисление значений функции на интервале с определённой точностью, после чего брать значения уже из таблицы. Программа сначала производит табулирование функции с заданными параметрами и сохраняет его в файл, затем подгружает предвычисленный массив, который уже используется для определения значений. В этой программе все функции возвращают переменную типа Result, которая хранит номер ошибки. Если функция отработала без проблем, то она возвращает Ok (0).
6. У нас имеются две структуры. Первая PersonKey хранит логин, пароль, id пользователя и поле offset. Вторая структура PersonInfo хранит имя и фамилию пользователя и его возраст. Первые структуры записываются в бинарный файл keys.bin, вторые структуры в бинарный файл values.bin. Поле offset определяет положение соответствующей информации о пользователе во втором файле. Таким образом, получив PersonKey из первого файла, по полю offset можно извлечь из второго файла связанную с данным ключом информацию.
Зачем так делать? Это выгодно в том случае, если структура PersonInfo имеет большой размер. Извлекать массив маленьких структур из файла не накладно, а когда нам понадобится большая структура, её можно извлечь по уже известному адресу в файле.
Всё ещё не понятно? – пиши вопросы на ящик
C Language
Файлы и потоки ввода-вывода
report this ad
параметр | подробности |
---|---|
const char * mode | Строка, описывающая режим открытия потока с файловой поддержкой. См. Замечания для возможных значений. |
int откуда | Может быть SEEK_SET для установки с начала файла SEEK_END для установки с его конца или SEEK_CUR для установки относительно текущего значения курсора. Примечание. SEEK_END не переносится. |
замечания
Строки режима:
Модификации режима в fopen() и freopen() могут быть одним из следующих значений:
- "r" : открыть файл в режиме только для чтения, при этом курсор установлен в начало файла.
- "r+" : открыть файл в режиме чтения-записи, при этом курсор установлен в начало файла.
- "w" : открыть или создать файл в режиме только для записи, при этом его содержимое обрезается до 0 байтов. Курсор установлен в начало файла.
- "w+" : открыть или создать файл в режиме чтения-записи, при этом его содержимое обрезается до 0 байтов. Курсор установлен в начало файла.
- "a" : открыть или создать файл в режиме только записи, при этом курсор установлен в конец файла.
- "a+" : открыть или создать файл в режиме чтения-записи, при этом указатель чтения установлен в начало файла. Выход, однако, всегда будет добавлен в конец файла.
Каждый из этих файловых режимов может иметь b добавленный после начальной буквы (например, "rb" или "a+b" или "ab+" ). b означает, что файл следует рассматривать как двоичный файл вместо текстового файла в тех системах, где есть разница. Это не влияет на Unix-подобные системы; это важно для систем Windows. (Кроме того, Windows fopen позволяет явному t вместо b указывать «текстовый файл» и множество других параметров для конкретной платформы.)
- "wx" : создать текстовый файл в режиме только для записи. Файл может не существовать .
- "wbx" : создать двоичный файл в режиме только для записи. Файл может не существовать .
x , если присутствует, должен быть последним символом в строке режима.
Открыть и записать в файл
Эта программа открывает файл с именем, указанным в аргументе main, по output.txt для output.txt если аргумент не указан. Если файл с тем же именем уже существует, его содержимое отбрасывается, и файл рассматривается как новый пустой файл. Если файлы еще не существуют, создается вызов fopen() .
Если по какой-либо причине вызов fopen() завершился с ошибкой, он возвращает значение NULL и устанавливает значение глобальной переменной errno . Это означает, что программа может проверить возвращаемое значение после вызова fopen() и использовать perror() если fopen() терпит неудачу.
Если вызов fopen() завершается успешно, он возвращает действительный указатель FILE . Этот указатель затем может использоваться для ссылки на этот файл до тех пор, пока на нем не будет fclose() .
Функция fputs() записывает данный текст в открытый файл, заменяя любое предыдущее содержимое файла. Аналогично функции fopen() функция fputs() также устанавливает значение errno если она терпит неудачу, хотя в этом случае функция возвращает EOF для указания отказа (иначе он возвращает неотрицательное значение).
Функция fclose() сбрасывает любые буферы, закрывает файл и освобождает память, на которую указывает FILE * . Возвращаемое значение указывает на завершение так же, как и fputs() (хотя при успешном завершении возвращает «0»), снова также устанавливая значение errno в случае сбоя.
fprintf
Вы можете использовать fprintf в файле так же, как на консоли с printf . Например, чтобы отслеживать выигрыши игр, потери и связи, которые вы могли бы написать
Замечание: некоторые системы (печально, Windows) не используют то, что большинство программистов назвали бы «нормальными» окончаниями строк. Хотя UNIX-подобные системы используют \ n для завершения строк, Windows использует пару символов: \ r (возврат каретки) и \ n (строка). Эта последовательность обычно называется CRLF. Однако при использовании C вам не нужно беспокоиться об этих деталях, зависящих от платформы. AC-компилятор необходим для преобразования каждого экземпляра \ n в правильную конечную строку платформы. Поэтому компилятор Windows будет конвертировать \ n в \ r \ n, но компилятор UNIX сохранит его как есть.
Выполнить процесс
Эта программа запускает процесс ( netstat ) через popen() и считывает весь стандартный вывод процесса и выводит его на стандартный вывод.
Примечание: popen() не существует в стандартной библиотеке C , но это скорее часть POSIX C )
Получить строки из файла с помощью getline ()
Библиотека POSIX C определяет функцию getline() . Эта функция выделяет буфер для хранения содержимого строки и возвращает новую строку, количество символов в строке и размер буфера.
Пример программы, которая получает каждую строку из example.txt :
Входной файл example.txt
Выход
В этом примере getline() изначально вызывается без выделенного буфера. Во время этого первого вызова getline() выделяет буфер, считывает первую строку и помещает содержимое строки в новый буфер. При последующих вызовах getline() обновляет один и тот же буфер и перераспределяет буфер только тогда, когда он больше не достаточно большой, чтобы соответствовать всей строке. Затем временный буфер освобождается, когда мы закончили с файлом.
Другой вариант — getdelim() . Это то же самое, что и getline() за исключением указания символа окончания строки. Это необходимо, только если последний символ строки для вашего типа файла не является \ n. getline() работает даже с текстовыми файлами Windows, потому что с завершением многобайтовой строки ( "\r\n") '\ n'` по-прежнему остается последним символом в строке.
Пример реализации getline()
Открыть и записать в двоичный файл
Эта программа создает и записывает текст в двоичной форме через функцию fwrite в файл output.bin .
Если файл с тем же именем уже существует, его содержимое отбрасывается, и файл рассматривается как новый пустой файл.
Бинарный поток представляет собой упорядоченную последовательность символов, которая может прозрачно записывать внутренние данные. В этом режиме байты записываются между программой и файлом без какой-либо интерпретации.
Чтобы записывать целые числа переносимым образом, необходимо знать, ожидает ли формат файла их в формате большой или малой длины, а также размер (обычно 16, 32 или 64 бита). Бит-сдвиг и маскирование могут затем использоваться для записи байтов в правильном порядке. Целые числа в C не гарантируют наличие двух дополняющих представлений (хотя почти все реализации выполняются). К счастью, преобразование в беззнаковый гарантированно использовать двойки комплемента. Поэтому код для записи знакового целого в двоичный файл немного удивителен.
Другие функции следуют одному и тому же шаблону с незначительными изменениями для размера и порядка байтов.
fscanf ()
Предположим, у нас есть текстовый файл, и мы хотим прочитать все слова в этом файле, чтобы выполнить некоторые требования.
file.txt :
Это основная функция:
Чтение строк из файла
Заголовок stdio.h определяет функцию fgets() . Эта функция считывает строку из потока и сохраняет ее в указанной строке. Функция прекращает чтение текста из потока, когда считывается n — 1 символ, читается символ новой строки ( '\n' ) или заканчивается конец файла (EOF).
Вызов программы с аргументом, который представляет собой путь к файлу, содержащему следующий текст:
Результатом будет следующий вывод:
Этот очень простой пример позволяет фиксированную максимальную длину строки, так что более длинные строки будут эффективно считаться двумя строками. Функция fgets() требует, чтобы вызывающий код предоставлял память, которая будет использоваться в качестве адресата для прочитанной строки.
POSIX делает доступной функцию getline() которая вместо этого внутренне выделяет память, чтобы увеличить буфер, если необходимо, для линии любой длины (при условии, что имеется достаточная память).
Класс BinaryWriter . Работа с бинарными файлами
Перед изучением данной темы рекомендуется ознакомиться со следующими темами:
Содержание
- 1. Класс BinaryWriter . Назначение
- 2. Взаимодействие класса BinaryWriter с потоками опорных хранилищ
- 3. Конструкторы класса BinaryWriter . Создание экземпляра
- 4. Основные методы класса BinaryWriter
- 4.1. Метод Write() — много перегруженных реализаций
- 4.1.1. Запись одиночных значений. Перегруженный метод Write() . Пример
- 4.1.2. Запись байтовых массивов byte[] в поток. Пример
- 4.1.3. Запись символьных массивов char[] в поток. Пример
Поиск на других ресурсах:
1. Класс BinaryWriter . Назначение
Класс BinaryWriter предназначен для записи данных в двоичном (бинарном) формате. Запись данных может осуществляться в файлы, сеть, изолированное хранилище, память и т.п. Во время записи строк существует возможность указания нужной кодировки. По умолчанию установлена кодировка UTF-8.
Класс реализован в пространстве имен System.IO . Для того, чтобы использовать возможности этого класса нужно добавить строкуКласс BinaryWriter записывает данные, которые представлены:
- примитивными типами: int , float , double и другими;
- строками типа string в указанной кодировке.
2. Взаимодействие класса BinaryWriter с потоками опорных хранилищ
Класс BinaryWriter (а также BinaryReader ) относится к адаптерам потоков. Это означает следующее. Чтобы получить доступ к файлу, сети или памяти, нужно использовать промежуточный класс опорного хранилища ( FileStream , MemoryStream , NetworkStream и т.д.).
На рисунке отображено взаимодействие класса BinaryWriter с файловым хранилищем, которому соответствует класс FileStream .Рисунок. Взаимодействие класса BinaryWriter с файлом через класс FileStream
3. Конструкторы класса BinaryWriter . Создание экземпляра
Класс BinaryWriter имеет несколько конструкторов, наиболее распространенными из которых следующие:
- output – ссылка на абстрактный класс Stream , являющийся вершиной иерархии классов ввода-вывода;
- encoding – система кодировки (Unicode, UTF32, UTF8 или другая). По умолчанию установлена система кодировки UTF8.
Пример. Для того, чтобы создать экземпляр класса BinaryWriter и связать его с файлом «abc.bin» нужно выполнить приблизительно следующий код.
Как видно из примера, при создании потоков используется синтаксис с использованием ключевого слова using() . Это освобождает от необходимости закрывать потоки методом Close() , поскольку при таком подходе очистка ресурсов происходит автоматически.
4. Основные методы класса BinaryWriter
4.1. Метод Write() — много перегруженных реализаций
Главный метод класса BinaryWriter — метод Write() . Этот метод позволяет записывать в поток данные всех примитивных (стандартных) типов.
Метод имеет 20 перегруженных реализаций, основные из которых приведены ниже.
4.1.1. Запись одиночных значений. Перегруженный метод Write() . Пример
Чтобы записать одиночные значения используются методы Write() , которые имеют следующую общую форму
здесь value – значение одного из стандартных (примитивных) типовв, которое нужно записать в поток.
Пример.
В примере в файл «data.bin» записываются данные разных типов с помощью метода Write() . Затем эти данные считываются с целью контроля.
Результат выполнения программы
4.1.2. Запись байтовых массивов byte[] в поток. Пример
Запись байт массивов типа byte[] полезен, когда в поток нужно записывать массивы стандартных типов (например, массивы int[] , double[] , decimal[] и т.д.). Для конвертирования из стандартных типов в тип byte[] удобно использовать возможности класса BitConverter .
- buffer – участок памяти, из которого будут записываться данные в поток побайтно;
- index – позиция (индекс) начала в массиве buffer , с которой будет происходить запись в поток;
- count – количество байт, которые нужно записать в поток.
Пример. В примере, с помощью метода Write() в файл «array.dat» записывается массив чисел типа double[] . Затем этот массив считывается и выводится на экран для сверки.
Результат выполнения программы
4.1.3. Запись символьных массивов char[] в поток. Пример
Для записи строк символов в виде массива char[] используются две следующие реализации метода Write() :
- chars – массив символов, который нужно записать в поток;
- index — позиция первого символа в массиве chars , начиная с которой нужно записать данные;
- count — количество символов, которые нужно прочитать из массива chars и записать в поток.
Пример. В примере записывается строка типа string в файловый поток в виде символьного массива char[] .
Результат выполнения программы
4.2. Метод Seek() . Пример
Метод Seek() устанавливает позицию в текущем потоке. Общая форма метода следующая:
- offset — смещение в байтах относительно позиции origin ;
- origin — значение, указывающее начальную точку, с которой должна быть получена новая позиция.
Значение origin есть перечислением и имеет тип
Значения из перечисления могут быть следующими:
- SeekOrigin.Begin — начальная точка отсчета устанавливается на начало потока (файла);
- SeekOrigin.End — начальная точка отсчета устанавливается в конец потока (файла);
- SeekOrigin.Current — определяется текущая позиция в потоке (файле).
Пример. В примере продемонстрированы следующие операции:
- запись чисел в файл;
- перезапись ранее записанных чисел;
- дописывание числа в конец файла.
Результат выполнения программы
4.3. Метод Flush() . Пример
Метод Flush() используется для очистки всех буферов текущего модуля записи. После этого можно записывать любые буферизированные данные на устройство, в которое на данный момент разрешено производить запись.
- 4.1. Метод Write() — много перегруженных реализаций