- Способы хранения информации: текстовый и двоичный
- Запись и чтение файлов в упрощенном режиме
- Открытие и закрытие файлов
- Управление дескрипторами файлов
- Выбор кодировки для текстового режима
- Запись и чтение массивов
- Запись и чтение структур (бинарные файлы)
- Запись и чтение переменных (бинарные файлы)
- Запись и чтение переменных (текстовые файлы)
- Управление позицией внутри файла
- Получение свойств файла
- Принудительная запись кэша на диск
- Удаление и проверка на существование файла
- Копирование и перемещение файлов
- Поиск файлов и папок
- Работа с папками
- Диалог выбора файла или папки
Запись и чтение файлов в упрощенном режиме
Среди файловых функций MQL5, которые предназначены для записи и чтения данных, существует разделение на 2 неравных группы. В первую из них входят две функции — FileSave и FileLoad, позволяющие записывать или считывать данные в двоичном режиме за один вызов функции. С одной стороны, данный подход имеет неоспоримое преимущество — простоту, но с другой — имеет некоторые ограничения (подробнее о них — чуть ниже). Во второй многочисленной группе все файловые функции используются иначе: требуется последовательно вызвать несколько из них, чтобы выполнить логически законченную операцию чтения или записи. Это кажется более сложным, но зато предоставляет гибкость и управление процессом. Функции второй групп оперируют особыми целыми числами — дескрипторами файлов, которые следует получить с помощью функции FileOpen (см. следующий раздел).
Познакомимся с формальным описанием этих двух функций, а затем рассмотрим их совместный пример (FileSaveLoad.mq5).
bool FileSave(const string filename, const void &data[], const int flag = 0)
Функция записывает в бинарный файл с именем filename все элементы переданного массива data. Параметр filename может содержать не только имя файла, но и имена папок нескольких уровней вложенности: функция создаст указанные папки, если их еще нет. Если файл существует, он будет перезаписан (если не занят другой программой).
В качестве параметра data может быть передан массив любых встроенных типов, кроме строк. Также это может быть массив простых структур, содержащих поля встроенных типов за исключением строк, динамических массивов и указателей. Классы также не поддерживаются.
Параметр flag может, при необходимости, содержать предопределенную константу FILE_COMMON, которая означает создание и запись файла в общий каталог данных всех терминалов (Common/Files/). Если флаг не указан (что соответствует значению по умолчанию 0), то файл пишется в обычный каталог данных (если MQL-программа выполняется в терминале) или в каталог агента тестирования (если дело происходит в тестере). В двух последних случаях, как было описано в начале главы, внутри каталога используется "песочница" MQL5/Files/.
Функция возвращает признак успеха операции (true) или ошибки (false).
long FileLoad(const string filename, void &data[], const int flag = 0)
Функция считывает всё содержимое бинарного файла filename в указанный массив data. Имя файла может включать иерархию папок внутри "песочницы" MQL5/Files или Common/Files.
Массив data должен иметь любой встроенный тип кроме string, или тип простой структуры (см. выше).
Параметр flag управляет выбором каталога, где ищется и открывается файл: по умолчанию (при значении 0) — в стандартной "песочнице", а если задано значение FILE_COMMON, то — в общей для всех терминалов.
Функция возвращает количество прочитанных элементов или -1 в случае ошибки.
Обратите внимание, что данные из файла читаются блоками размером в один элемент массива. Если размер файла оказывается некратным размеру элемента, то оставшиеся данные пропускаются (не читаются). Например, при размере файла 10 байтов его чтение в массив типа double (sizeof(double)=8) приведет к тому, что фактически будет загружено только 8 байтов, то есть 1 элемент (и функция вернет 1). Оставшиеся 2 байта в конце файла будут проигнорированы.
В скрипте FileSaveLoad.mq5 определим две структуры для тестов.
struct Pair
|
Структура Simple содержит поля большинства разрешенных типов, а также составное поле с типом структуры Pair. В функции OnStart заполним небольшой массив типа Simple.
void OnStart()
|
Файл для записи данных выберем вместе с вложенной папкой "MQL5Book", чтобы наши эксперименты не перемешивались с вашими рабочими файлами:
const string filename = "MQL5Book/rawdata"; |
Запишем массив в файл, прочитаем его в другой массив и сравним их.
PRT(FileSave(filename, write/*, FILE_COMMON*/)); // true
|
FileLoad вернула 2, то есть было прочитано 2 элемента (2 структуры). Результат сравнения 0 означает, что данные совпали. Вы можете в своем любимом файловом менеджере открыть папку MQL5/Files/MQL5Book и убедиться, что там есть файл "rawdata" (смотреть его внутренности текстовым редактором не рекомендуется, используйте программу просмотра с поддержкой двоичного режима).
Далее в скрипте мы преобразуем прочтенный массив структур в байты и выводим их в журнал в виде шестнадцатеричных кодов: это своего рода дамп памяти — он позволяет понять, что из себя представляют бинарные файлы.
uchar bytes[];
|
Результат:
[00] 00 | 00 | 00 | 00 | 00 | 00 | F0 | 3F | FF | FF | FF | FF | 00 | 66 | EE | 5F |
|
Поскольку встроенная функция ArrayPrint не умеет "печатать" в шестнадцатеричном формате, нам пришлось разработать собственную функцию ByteArrayPrint (здесь её исходный код не будем приводить, см. прилагаемый файл).
Далее, вспомним, что FileLoad способна загрузить данные в массив любого типа, поэтому прочтем тот же файл с помощью неё непосредственно в массив байтов.
uchar bytes2[];
|
Успешное сравнение двух байтовых массивов показывает, что FileLoad "не стесняется" оперировать сырыми данными из файла произвольным образом: так, как ему "скажут" (в файле нет информации о том, что он хранит массив именно структур Simple).
Здесь важно отметить, что поскольку тип байт имеет минимальный размер (1), то ему кратен любой размер файла. Поэтому в байтовый массив любой файл всегда читается без остатка. Функция FileLoad вернула здесь число 78 (число элементов равно числу байтов). Это размер файла (две структуры по 39 байтов каждая).
В принципе, способность FileLoad интерпретировать данные под любой тип требует осторожности и проверок со стороны программиста. В частности, далее в скрипте мы читаем тот же файл в массив структур MqlDateTime. Это, конечно же, неправильно, но работает без ошибок.
MqlDateTime mdt[];
|
Результат содержит бессмысленный набор чисел:
[year] [mon] [day] [hour] [min] [sec] [day_of_week] [day_of_year]
|
Поскольку размер MqlDateTime равен 32, то в файле длиной 78 байтов укладывается только две таких структуры, причем лишними остаются еще 14 байтов. Само наличие остатка свидетельствует о проблеме. Но даже если его нет, это не гарантирует осмысленность выполненной операции, потому что два разных размера могут чисто случайно укладываться целое (но разное) число раз в длину файла. Более того, две разных по смыслу структуры могут иметь одинаковый размер, но это не значит, что их следует записывать и читать из одной в другую.
Неудивительно, что вывод в журнал массива структур MqlDateTime показывает "сумасшедшие" величины: ведь это был, на самом деле, совсем другой тип данных.
Чтобы сделать чтение в некоторой степени более "осторожным", в скрипте реализован аналог функции FileLoad — MyFileLoad. Детально мы разберем эту функцию, а также парную ей MyFileSave, в следующих разделах, когда изучим новые файловые функции и с их помощью смоделируем внутреннее устройство FileSave/FileLoad. А пока лишь отметим, что в своей версии мы можем проверять наличие непрочитанного остатка в файле и выводить предупреждение.
В заключение разберем еще пару потенциальных ошибок, продемонстрированных в скрипте.
/*
|
Первая из них происходит во время компиляции (в связи с чем блок кода закомментирован), потому что строковые массивы запрещены.
Вторая заключается в чтении несуществующего файла, из-за чего FileLoad возвращает -1. Пояснительный код ошибки легко получить с помощью GetLastError (или _LastError).