Структуры и алгоритмы компьютерной обработки данных

Прямой доступ к данным файла

Показывать лекцию целиком

Цель лекции: изучить организацию ввода-вывода в файлы на нижнем уровне, научиться решать задачи с использованием прямого доступа к данным файла на языке C++.

Ввод-вывод низкого уровня

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

Рассмотренные ранее средства обмена с файлами позволяют записывать и считывать данные только последовательно. Операции чтения-записи всегда производятся, начиная с текущей позиции в потоке. Начальная позиция устанавливается при открытии потока и может соответствовать начальному или конечному байту потока в зависимости от режима открытия файла. При этом данные потока буферизируются и выполняется форматирование передаваемой информации.

Функции ввода-вывода низкого уровня (прямого доступа) осуществляют обмен с файлами или периферийными устройствами путем прямого обращения к соответствующим функциям операционной системы (системным вызовам). Отличительные особенности средств прямого доступа к файлам следующие.

  • Они не предоставляют возможности буферизации информации при пересылке.
  • Они не обеспечивают преобразования данных из внутреннего машинного представления в текстовый формат.
  • Они дают возможность перемещать указатель текущей позиции в потоке на нужный байт.
  • При низкоуровневом открытии файла с ним связывается файловый дескриптор. Дескриптор является целым значением, характеризующим размещение информации об открытом файле во внутренних таблицах операционной системы. Дескриптор используется при последующих операциях с файлом.
  • Основные функции низкого уровня

    Функции низкого уровня, прототипы которых входят в стандартную библиотеку <io.h>, обычно применяются при разработке собственных подсистем ввода-вывода. Большинство функций этого уровня переносимы в рамках некоторых систем программирования на язык С или С++.

    Функция открытия файла для чтения-записи

    Для начала работы с файлом его необходимо открыть с помощью функции open.

    Синтаксис:

    int open(const char *filename, int oflags [,int sflags]);

    где filename – указатель на строку символов, представляющую собой допустимое имя файла, в которое может входить спецификация файла (включает обозначение логического устройства, путь к файлу и собственно имя файла). При указании полного пути в качестве разделителя используется символ "слэш" ('/'), а не "обратный слэш" ('\'), как принято. Это объясняется использованием символа "обратный слэш" в управляющих последовательностях в С++;

    oflags – доступный тип операций, представляющий собой одну или несколько целочисленных констант, объявленных в файле <fcntl.h>. Если задана больше чем одна константа, тогда выполняется их объединение при помощи логического оператора ИЛИ ( | ). Состав доступных констант зависит от операционной системы. В таблице перечислены константы режима, встречающиеся практически во всех операционных системах.

    Константы типа операций
    Константа Описание
    O_APPEND Добавление в конец файла. Указатель на файл перемещен в конец файла перед каждой операцией записи.
    O_CREAT Новый файл создается и открывается для записи; не эффективно, если существует файл, определяемый по указанному имени.
    O_EXCL Возвращается значение ошибки, если существует файл, определяемый по указанному имени. Применяется только вместе с O_CREAT.
    O_RDONLY Файл открыт только для чтения; если задается этот флаг, может быть выбран либо флаг O_RDWR, либо O_WRONLY.
    O_WRONLY Файл открыт только для записи; если задан этот флаг, должен быть задан также либо флаг O_RDONLY, либо O_RDWR.
    O_RDWR Файл открыт одновременно для чтения и записи; если задается этот флаг, может быть выбран либо флаг O_RDONLY, либо O_WRONLY.
    O_TRUNC Существующий файл открыт и усечен к длине 0; этот файл должен иметь разрешение на запись. Содержимое файла уничтожается.
    O_BINARY Файл открыт в двоичном (не транслированном) режиме.
    O_TEXT Файл открыт в текстовом (транслирующем) режиме.

    sflags – необязательный параметр, который определяет тип доступа к файлу и представляет собой одну или несколько целочисленных констант, объявленных в файле <sys\stat.h>. Если задана больше чем одна константа, тогда выполняется их объединение при помощи логического оператора ИЛИ ( | ). Данный параметр применяется совместно с константой O_CREAT типа операций. Если открываемый файл существует, ТипДоступа игнорируется.

    Константы типа доступа
    Константа Описание
    S_IWRITE Разрешена запись
    S_IREAD Разрешено чтение
    S_IREAD|S_IWRITE Разрешены чтение и запись

    В случае успешного открытия файла данная функция возвращает неотрицательное целое значение, которое соответствует логическому номеру файла, а указатель устанавливается на начало файла. Максимальное число одновременно открытых файлов определяется константой HANDLE_MAX. При возникновении ошибки открытия файла функция возвращает значение -1.

    Функция открытия файла для разделенного доступа

    Семантика разделения означает, что файловая система должна определить алгоритм работы, который применяется, когда несколько клиентов одновременно обращаются к одному файлу. Важно, чтобы все изменения, сделанные одним клиентом, были бы видны другим клиентам, когда они выполняют следующий системный вызов на чтение или запись в один и тот же файл. Открытие файлов для разделенного доступа к ним выполняется с помощью функции sopen.

    Синтаксис:

    int sopen(const char *filename, int oflags, int shflags [,int sflags]);

    где параметры filename, oflags, sflags имеют тот же смысл, что и в функции open.

    shflags - устанавливаемый тип разделенного доступа к файлу, представляющий собой одну из целочисленных констант, объявленных в файле <fcntl.h>.

    Константы типа разделения
    Константа Описание
    SH_COMPAT устанавливается режим совместимости
    SH_DENYRW доступ по чтению и записи в файле не разрешен
    SH_DENYWR доступ по записи в файле не разрешен
    SH_DENYRD доступ по чтению в файле не разрешен
    SH_DENYNO доступ по чтению и записи разрешен

    В случае успешного открытия файла данная функция возвращает неотрицательное целое значение, которое соответствует логическому номеру файла, а указатель устанавливается на начало файла. При возникновении ошибки открытия файла функция возвращает значение -1.

    Функция создания файла

    Функция open предоставляет доступ к существующему файлу или создает его заново, а функция creat создает в файловой системе новый объект.

    Синтаксис:

    int creat(const char *filename, int sflags);

    где параметры filename, sflags имеют тот же смысл, что и в функции open.

    Если прежде в файловой системе не существовало объекта с указанным именем или полной спецификацией, будет создан новый файл с указанным именем и указанными правами доступа к нему. Если же такой файл уже существовал, размер файла усекается до 0 (освобождаются все существующие блоки данных и устанавливается размер файла равным 0). Созданный ранее файл должен при этом позволять производить запись в него, чтобы можно было создать "новый" файл с тем же самым именем.

    Функция закрытия файла

    После завершения работы с файлом его необходимо закрыть. Для этого используется функция close.

    Синтаксис:

    close(int fd);

    где fd - дескриптор открытого файла.

    Ядро выполняет операцию закрытия, используя дескриптор файла и информацию из соответствующих записей в таблице файлов и таблице индексов. Если счетчик ссылок в записи таблицы файлов имеет значение, большее, чем 1, то это означает, что на запись в таблице файлов делают ссылку другие пользовательские дескрипторы (например, при разделенном доступе). В этом случае счетчик ссылок уменьшается на 1. Если счетчик ссылок в таблице файлов имеет значение, равное 1, операционная система освобождает запись в таблице и индекс в памяти, ранее выделенный системной функцией open.

    Когда выполнение функции close завершается, запись в таблице пользовательских дескрипторов файла становится пустой. Попытки процесса использовать данный дескриптор заканчиваются ошибкой до тех пор, пока дескриптор не будет переназначен другому файлу в результате выполнения другой функции. Когда выполнение программного кода завершается, система проверяет наличие активных пользовательских дескрипторов файла данного сеанса и закрывает каждый из них. Таким образом, ни один сеанс выполнения кода не может оставить файл открытым после своего завершения.

    Функция чтения из файла

    Чтение из файла на нижнем уровне осуществляется побайтно, без буферизации, с помощью функции read.

    Синтаксис:

    int read(int fd, char *buffer,int count);

    где fd - дескриптор файла, возвращаемый функцией open ;

    buffer – адрес структуры данных, определенной пользователем, где будут размещаться считанные данные в случае успешного завершения выполнения функции read ;

    count - количество байтов, которые определяет пользователь для считывания.

    Функция возвращает количество фактически прочитанных байтов. В процессе выполнения функции ядро операционной системы обращается в таблице файлов к записи, которая соответствует значению пользовательского дескриптора файла, следуя за указателем. Затем оно устанавливает значения нескольких параметров ввода-вывода в адресном пространстве, тем самым устраняя необходимость в их передаче в качестве параметров функции. При выполнении режима ввода-вывода "чтение" формируются значения следующих параметров:

  • устанавливается флаг, свидетельствующий о том, что ввод-вывод направляется в адресное пространство структуры пользователя;
  • значению поля счетчика байтов присваивается количество байтов, которые должны быть прочитаны;
  • устанавливается адрес пользовательского буфера данных;
  • определяется значение смещения (из таблицы файлов), равное смещению в байтах внутри файла до места, откуда начинается ввод-вывод.
  • После считывания данные из блока копируются по назначенному адресу в структуру пользователя. При этом корректируются параметры ввода-вывода в адресном пространстве в соответствии с количеством прочитанных байтов: увеличивается значение смещения в байтах внутри файла и адрес структуры пользователя, куда будет доставлена следующая порция данных. Одновременно уменьшается число байтов, которые необходимо прочитать, чтобы выполнить запрос пользователя. Если запрос на чтение полностью не выполнен, происходит циклическое повторение описанных действий. Цикл завершается при достижении хотя бы одного из условий:

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

    Функция записи в файл

    Запись в файл на нижнем уровне осуществляется побайтно, без буферизации, с помощью функции write.

    Синтаксис:

    int write(int fd, char *buffer,int count);

    где переменные fd, buffer, count имеют тот же смысл, что и для вызова функции read.

    Функция возвращает количество фактически переданных байтов.

    Алгоритм записи в файл похож на алгоритм чтения: указатель циклически перемещается в соответствии с количеством переданных в файл байтов до завершения процесса передачи данных в файл. Когда запись завершается, операционная система корректирует размер файла.

    Функция прямого доступа к файлу

    Функция read при корректном выполнении осуществляет последовательное чтение данных из файла. Однако при работе с файлами на нижнем уровне можно изменять порядок доступа к данным с последовательного на произвольный. Для этого используется функция fseek.

    Синтаксис:

    int fseek(FILE *f, long off, int org);

    где f – указатель на файл;

    off – позиция смещения;

    org – начало отсчета.

    Смещение off задается выражением или переменной и может быть отрицательным, то есть возможно перемещение как в прямом, так и в обратном направлениях. Начало отсчета org задается одной из определенных в файле <stdio.h> констант.

    Константы, задающие начало отсчета
    Константа Значение Описание
    SEEK_SET 0 начало файла
    SEEK_CUR 1 текущая позиция
    SEEK_END 2 конец файла

    Функция возвращает 0, если перемещение в потоке выполнено успешно, иначе возвращает ненулевое значение.

    Функцию fseek следует использовать только при работе с двоичными файлами. В текстовых файлах начало отсчета org должно быть установлено как SEEK_SET, а смещение указывается относительно начала файла (возможно нулевое смещение, которое интерпретируется как позиционирование в начало файла).

    Например, для текстовых файлов:

    fseek(f,0L,SEEK_SET); 
    //перемещение к началу потока из текущей позиции
    fseek(f,0L,SEEK_END); 
    //перемещение к концу потока из текущей позиции

    Например, для двоичных файлов:

    fseek(f,(long)sizeof(a),SEEK_SET); 
    //перемещение вперед от начала на длину переменной а
    fseek(f,-(long)sizeof(a),SEEK_CUR); 
    //перемещение назад от текущей позиции на длину переменной а

    Кроме этой функции, для прямого доступа к файлу используются функции:

    long ftell(FILE *f);
    //получает значение указателя текущей позиции в потоке
    void rewind(FILE *f);
    //устанавливает значение указателя на начало потока

    Пример 1. Использование в программе функций доступа к данным на нижнем уровне.

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    #include <fcntl.h> 
    #include <sys\stat.h>
    #include <io.h>
    int _tmain(int argc, _TCHAR* argv[]){
      int fh1, fh2;
      //открытие файла на нижнем уровне
      fh1 = open("data1.dat", O_RDONLY);
      //проверка корректности открытия файла
      if (fh1 == -1)
        perror("Open failed on input file");
      fh2 = open("data2.dat",O_WRONLY|O_TRUNC|O_CREAT,S_IREAD|S_IWRITE);
      if (fh2 == -1)
        perror("Open failed on output file");
      system("pause");
      return 0;
    }

    Пример 2. Использование в программе позиционирования файлового указателя с помощью функции fseek в двоичном файле.

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    #include <io.h>
    int _tmain(int argc, _TCHAR* argv[]){
      FILE *f;//указатель на двоичный файл
      int i,n=10;
      char s[]="String";
      float r;
      f=fopen("file_bin","wb");
      //создание двоичного файла для записи
      for(i=1;i<=n;i++){ 
        r=pow(i,1.0/3);
        fwrite(s,sizeof(s),1,f);
        //запись строки String в файл
        fwrite(i,sizeof(int),1,f);
        //запись целого числа (номера строки) в файл
        fwrite(r,sizeof(float),1,f);
        //запись вещественного числа (корня кубического) в файл
        printf("\n%s %d %f",s,i,r);//контрольный вывод на экран
      }
      fclose(f);//закрытие файла
      printf("\n");
      f=fopen("file_bin","rb");
      //открытие двоичного файла для чтения
      for(i=n; i>0; i--) {
        //перемещение указателя файла
        fseek(f,(i-1)*(sizeof(s)+sizeof(int)+sizeof(float)),SEEK_SET);
        fread(s,sizeof(s),1,f);//чтение строки
        fread(n,sizeof(int),1,f);//чтение целого числа
        fread(r,sizeof(float),1,f);
        //чтение вещественного числа
        printf("\n%s %d %f",s,n,r);
        //вывод на экран содержимого файла
      }  
      system("pause");
      return 0;
    }

    Ключевые термины

    Дескриптор – это целое значение, характеризующее размещение информации об открытом файле во внутренних таблицах операционной системы.

    Прямой доступ к файлу – это доступ к содержимому файла с помощью средств операционной системы.

    Разделенный доступ к файлу – это организация работы с одним и тем же файлом нескольких пользователей одновременно.

    Смещение – это относительная координата перемещения указателя при обращении к данным файла на нижнем уровне.

    Тип доступа – это разрешенное действие над файлом, оформленное с помощью целочисленных констант.

    Тип операций – это выполняемая операция над файлом, оформленная с помощью целочисленных констант.

    Функции ввода-вывода низкого уровня (прямого доступа) – это функции, осуществляющие обмен с файлами или периферийными устройствами путем прямого обращения к соответствующим функциям операционной системы.

    Краткие итоги

  • В С++ доступ к файлам можно осуществлять на потоковом и низком уровнях.
  • В отличие от потокового уровня, низкоуровневый ввод-вывод осуществляется побайтно без буферизации данных, без приведения их к базовым типам и использует ресурсы операционной системы.
  • Работа с файлами на низком уровне осуществляется с помощью средств прямого доступа к данным файла.
  • Открытому файлу на нижнем уровне соответствует файловый дескриптор.
  • Режимы доступа к данным файла на нижнем уровне определяются с помощью констант типа доступа и типа операций.
  • В С++ предусмотрен разделенный режим работы с файлами на нижнем уровне.
  • Открытый файл должен быть закрыт после использования.
  • Порядок доступа к байтам файла можно изменять функционально путем указания относительного смещения. Смещение может быть положительным (движение к концу файла), отрицательным (движение к началу файла) или нулевым (нет смещения).
  • Запрос на чтение данных на нижнем уровне завершается при считывании заданного числа байтов, при достижении конца файла или при возникновении системной ошибки чтения.
  • Лабораторная работа 21. Прямой доступ к данным файла

    Цель работы: изучить организацию ввода-вывода в файлы на нижнем уровне, научиться решать задачи с использованием прямого доступа к данным файла на языке C++.

    При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на входе одну (в задачах 2 и 4) или две (в задачах 1 и 3) строковые величины, являющиеся корректными именами входного и выходного файлов (при этом в программе следует предусмотреть проверку существования и корректности открытия используемых файлов). Ввод данных осуществляется из входного файла на нижнем уровне с учетом требований к входным данным, содержащихся в постановке задачи. Ограничениями на входные данные является максимальный размер строковых данных и допустимый диапазон числовых типов в языке С++. При некорректном открытии файла программа должна завершить работу с выдачей на экран соответствующего сообщения.

    Теоретические сведения.

    Ознакомьтесь с материалом лекции 21.

    Задания к лабораторной работе.

    Выполните приведенные ниже задания.

  • Составьте программу, копирующую содержимое данного файла побайтно в другой файл.
  • Составьте программу, определяющую размер файла в байтах с использованием функций прямого доступа.
  • Составьте программу, копирующую содержимое данного файла побайтно в обратном порядке.
  • Составьте программу, которая выводит свой текст на экран. Используйте работу с файлами на нижнем уровне.
  • Указания к выполнению работы.

    Каждое задание необходимо решить в соответствии с изученными методами обработки файлов на нижнем уровне в языке С++. В программном коде к каждой задаче следует выполнять чтение данных из существующего входного файла (имя каждого файла вводится в виде строки символов и считается корректным с точки зрения организации файловой системы). В задачах 1 и 3 необходимо использовать вспомогательный файл, который выступает в роли выходного файла. Данные входного и выходного файлов необходимо дублировать на экране с соответствующими комментариями. Программу для решения каждого задания необходимо разработать методом процедурной абстракции, оформив комментарии к коду.

    Следует реализовать каждое задание в соответствии с приведенными этапами:

  • изучить словесную постановку задачи, выделив при этом все виды данных;
  • сформулировать математическую постановку задачи;
  • выбрать метод решения задачи, если это необходимо;
  • разработать графическую схему алгоритма;
  • записать разработанный алгоритм на языке С++;
  • разработать контрольный тест к программе;
  • отладить программу;
  • представить отчет по работе.
  • Требования к отчету.

    Отчет по лабораторной работе должен соответствовать следующей структуре.

  • Титульный лист.
  • Словесная постановка задачи. В этом подразделе проводится полное описание задачи. Описывается суть задачи, анализ входящих в нее физических величин, область их допустимых значений, единицы их измерения, возможные ограничения, анализ условий при которых задача имеет решение (не имеет решения), анализ ожидаемых результатов.
  • Математическая модель. В этом подразделе вводятся математические описания физических величин и математическое описание их взаимодействий. Цель подраздела – представить решаемую задачу в математической формулировке.
  • Алгоритм решения задачи. В подразделе описывается разработка структуры алгоритма, обосновывается абстракция данных, задача разбивается на подзадачи. Схема алгоритма выполняется по ЕСПД (ГОСТ 19.003-80 и ГОСТ 19.002-80).
  • Листинг программы. Подраздел должен содержать текст программы на языке программирования С++, реализованный в среде MS Visual Studio 2010.
  • Контрольный тест. Подраздел содержит наборы исходных данных и полученные в ходе выполнения программы результаты.
  • Выводы по лабораторной работе.
  • Ответы на контрольные вопросы.
  • Контрольные вопросы

  • Почему в С++ возможен низкоуровневый ввод-вывод в файлы?
  • Каким способом в программе происходит идентификация открытых файлов?
  • Как при разделенном доступе к файлу происходит контроль числа клиентов?
  • С какой целью в функции открытия файла на нижнем уровне предусмотрены параметры-константы? Почему их такое многообразие?
  • Почему при низкоуровневом вводе-выводе возможно менять порядок доступа к данным файла?
  • Каким образом контролируется положение указателя в файле при побайтовом чтении или при смещении?
  • Куда передается считанная из файла информация при низкоуровневом чтении данных?
  • В чем отличие следующих действий: открытие существующего файла с усечением его длины до нуля и создание файла?
  • Сохранится ли при низкоуровневом доступе информация в файле, если его не закрыть в программе с помощью функции?
  • Вернуться к учебному плану