Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Обработка ошибок ввода-вывода

Основы работы с файлами

Трудно переоценить умение работать с файлами, ведь это одна из наиболее часто встречающихся в программировании задач. По большому счету можно утверждать, что вся работа компьютера сводится к манипуляциям с файлами, а точнее данными, содержащимися в них. Практически любая создаваемая программа (кстати, тоже как минимум состоящая из одного файла)должна взаимодействовать с файловой системой компьютера и, более того, осуществлять базовые операции ввода-вывода (I/O routines).

Что понимается под операциями ввода-вывода? Это действия, связанные с созданием нового или открытием существующего файла, с операциями чтения из файла и записью в него информации, с копированием, перемещением и удалением файла и т. д. Кроме того, в системе Windows умение работать с файлами весьма пригодится при обращении к именованным каналам, почтовым слотам и сокетам.

Данная глава посвящена основным приемам работы с файлами и файловой системой. Прежде чем перейти к основному материалу, стоит отметить, что в библиотеке визуальных компонентов VCL среды программирования Delphi реализован целый спектр компонентов, значительно упрощающих организацию доступа к каталогам и файлам (см. главу 14 «Диалог с Windows»). Вместе с тем ряд элементов управления инкапсулирует методы, предоставляющие программисту достаточно совершенный механизм загрузки и сохранения данных. К ним относятся мемо-поле TMemo, расширенный текстовый редактор TRichEdit, рисунок TImage, списки TListBox и TComboBox и еще достаточно обширный перечень компонентов, изучаемых в дальнейших главах.

 

Классификация типов файлов

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

1. Текстовые файлы.

2. Типизированные файлы.

3. Двоичные (нетипизированные) файлы.

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

Работа с любым типом файла в языке Pascal требует определения так называемой файловой переменной, которая будет выступать в качестве аргумента во многих методах ввода-вывода. Способ объявления файловой переменной определяется типом файла, на который будет указывать наша переменная. Например, подготовка переменной для работы с текстовым файлом выглядит следующим образом:

 

var MyFile : TextFile;

 

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

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

 

var F : file of <тип файла>;

 

Приведем пример объявления трех файловых переменных, нацеленных на работу c файлами целых чисел, вещественных чисел и записей соответственно.

 

var Int_File : file of Integer; //файл типа Integer

Real_File : file of Real; //файл типа Real

Record_File : file of TPoint; //файл для работы с записями типа TPoint

 

Синтаксис языка Pascal не запрещает создание типизированных файлов массивов, хотя в общем-то файл сам по себе является массивом.

Если в определении типизированного файла используются строковые данные string, то необходимо определить количество символов, которое планируется хранить в одной записи. Ограничение указывается в квадратных скобках после ключевого слова string[xx].

Самый универсальный формат файла – двоичный. Это файлы графики, аудио и видеофайлы, электронные таблицы, HTML-файлы. Короче говоря, все существующие файлы. Как видите, текстовые и типизированные файлы представляют собой частный случай двоичного файла.

Для объявления файловой переменной двоичного файла применяют следующий синтаксис:

 

var DataFile: File;

 

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

Типом файла определяются особенности применения методов, реализующих базовые операции ввода-вывода.

 

Чтение и запись данных

Мы уже знаем, что базовые операции ввода-вывода (Input-Output, I/O) в первую очередь нацелены на решение задач чтения из файла и записи в файл требуемых данных. При реализации этих операций любой язык программирования требует от нас особой внимательности и осторожности. Во-первых, следует учитывать, что перечень применяемых методов ввода-вывода определяется типом файла, с которым планируется работа. Во-вторых, необходимо соблюдать строго определенную последовательность вызова методов. В-третьих, в своем коде программист должен предусмотреть и тщательно реализовать систему обработки потенциальных ошибок.

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

var F : file;

Begin

AssignFile(F,'C:\MyFile.dat'); // Связывание файловой переменной F с файлом

try

//Секция для потенциально опасных операций над файлом

finally

CloseFile(F); // Закрытие файла и освобождение переменной

end;

End.

Прокомментирую эти строки. Нами объявлена файловая переменная с именем F. Первая процедура в листинге предназначена для связывания файловой переменной F с именем файла FileName. Эту процедуру дальше будем называть процедурой инициализации базовых операций ввода-вывода.

 

procedure AssignFile(var F; FileName: string);

 

Теперь файловая переменная в состоянии держать связь с этим файлом до тех пор, пока не будет освобождена. Для разрыва связи и освобождения занятых ресурсов необходим вызов процедуры закрытия файла:

 

procedureCloseFile(var F);

 

Указанный метод завершает операции ввода-вывода. Обе процедуры всегдаприменяются в паре, и если в программе был осуществлен вызов AssignFile(), то в заключение позаботьтесь и об обязательном вызове CloseFile(). Однако при работе с файлом всегда высока вероятность появления целого спектра самых разнообразных ошибок, которые могут привести к возникновению исключительной ситуации. Именно поэтому в примере процедура закрытия вынесена в секцию finally обработчика ошибки try..finally. Такой подход гарантирует освобождение файловой переменной даже при возникновении ошибок. Более подробно особенности конструкции try..finally рассматриваются в главе 15.

Ни в коем случае не пытайтесь вызвать метод AssignFile() для файловой переменной, уже ссылающейся на другой открытый файл. При программировании всегда руководствуйтесь правилом: каждому файлу – индивидуальная файловая переменная.

 

Текстовые файлы

После того как мы связали файловую переменную с именем файла, в зависимости от поставленных задач можно пойти по одному из трех путей (см. алгоритм на рис. 4.1):

 

1. Создание нового тестового файла.

 

2. Открытие существующего файла в режиме только для чтения.

 

3. Открытие существующего файла с возможностью добавления новых строк.

 

 

Для реализации этих задач в языке Pascal предусмотрено три процедуры: Rewrite(), Reset() и Append(). Для создания нового файла вызовите метод:

 

procedure Rewrite(var F: File [; RecSize: Word ] );

 

Ключевой параметр процедуры – файловая переменная F. В результате выполнения метода формируется пустой файл с именем, определенным в процедуре инициализации AssignFile(). Если вдруг имя вновь создаваемого файла совпадет с именем существующего, то старый файл стирается без всякого предупреждения и сожаления. Второй необязательный параметр определяет, какими порциями будут осуществляться операции записи данных в файл. По умолчанию назначается размер, равный 128 байт. При работе с текстовыми файлами этот параметр не используется, но играет важную роль для двоичных файлов.

Для открытия уже существующего файла процедура Rewrite() не нужна. Вместо нее воспользуйтесь методом инициализации чтения:

 

procedureReset(var F [: File; RecSize: Word ] );

 

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

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

 

procedure Append(var F: Text);

 

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

Осталось привести несколько простых поясняющих примеров. Первый из них создает новый файл в корне диска C:\ и заполняет его десятью символами A.

 

programDemoTxt1;

{$APPTYPE CONSOLE}

uses SysUtils;

varF : TextFile;

i : integer;

Begin

AssignFile(F,'C:\NewFile.txt');

Try

Rewrite(F);

for i:=0 to 9 do Write(F,'A');

Finally

CloseFile(F);

end;

End.

Второй пример – скромная консольная программка, осуществляющая вывод содержимого текстового файла autoexec.bat с последующим отображением прочитанной информации на экран компьютера.

 

program DemoTxt2;

{$APPTYPE CONSOLE}

uses SysUtils;

var F : TextFile;

s : string;

Begin

AssignFile(F,'C:\Autoexec.bat');

Reset(F);

TRY

while SeekEof(f)=False do //проверка местоположения указателя

begin //пока указатель не достиг последней строки файла

ReadLn(F,S); //считываем очередную строку в переменную S

WriteLn(s); //и отображаем считанную информацию на экране

end;

FINALLY

CloseFile(F);

END;

ReadLn;

End.

Третий пример добавляет десять строк в конец текстового файла. Он также не должен вызвать особых затруднений, только не забудьте перед запуском программы создать в корне диска <C:> файл MyFile.txt, иначе программа выразит свое недоумение по поводу отсутствия открываемого файла.

 

program DemoTxt3;

{$APPTYPE CONSOLE}

uses SysUtils;

var F : TextFile;

i : integer;

Begin

AssignFile(F,'C:\MyFile.txt');

Append(F);

TRY

for i:=0 to 9do WriteLn(F,'Строка -', i);

Flush(f);

FINALLY

CloseFile(F);

END;

ReadLn

End.

 

Уверен, что в предложенных листингах внимательный читатель заметил ряд новых методов. В первую очередь это процедуры, осуществляющие чтение Read() и запись Write() данных.

Процедуры Read() и Write() весьма часто применяют в консольных приложениях для обычного взаимодействия с пользователем, т. к. последние способны работать не только с файлами, но и с клавиатурой и консолью.

 

program DemoCns;

{$APPTYPE CONSOLE}

usesSysUtils;

var s : string;

Begin

Write('Insert name: ');

Readln(s);

Writeln('Name: ',s);

Writeln('Press any key to exit');

Readln;

end;

End.

procedureRead( [ var F: Text; ] V1 [, V2,...,Vn ] );

procedure ReadLn([ var F: Text; ] V1 [, V2,...,Vn]);

procedure Write([ var F: Text; ] P1 [, P2,..., Pn]);

procedure WriteLn([ var F: Text; ] P1 [, P2,...,Pn]);

 

Все четыре процедуры вооружены идентичными параметрами. Первый аргумент, F, соответствует файловой переменной текстового файла. В качестве второго аргумента в процедурах чтения указывают переменную, в которую осуществляется чтение данных из файла, а в процедурах записи, наоборот, данные, содержащиеся в этой переменной, записываются в файл. Обратите внимание на ряд необязательных параметров в квадратных скобках [, V2,...,Vn].

При желании программист может ими воспользоваться, как в примере добавления строк в конец файла, где помимо текстовой информации 'Строка -' в файл записывается номер шага в цикле. Окончание …ln (от англ. line) в имени процедуры говорит нам о том, что процедура нацелена на работу со строками.

Очистка буфера текстового файла осуществляется методом:

 

procedure Flush (var F: Text);

 

Вызов этого метода целесообразен в том случае, когда файл был открыт для записи. Функция гарантирует, что все данные из буфера будут сохранены в файл на диске. Эта процедура вызывается по окончании операций записи данных в файл.

Познакомимся с еще одной весьма полезной функцией с именем Eof() (сокращение от англ. End of file), в чьи обязанности входит проверка того, не находится ли внутренний указатель файла на самой последней его строке.Другими словами, не достигнут ли еще конец файла.

 

function Eof[ (var F: Text) ]: Boolean;

 

Этот метод обычно применяется при организации чтения данных из файла. Вернитесь к листингу проекта, демонстрирующего способ чтения файлаautoexec.bat. Здесь метод Eof() вызывается внутри цикла while…do. Пока функция возвращает значение false (признак того, что конец файла еще не достигнут), продолжается выполнение операций чтения. Каждый вызов метода WriteLn() заставляет указатель файла перемещаться на одну строку вперед, все ближе и ближе к концу файла.

Специально для текстовых файлов реализован «продвинутый» метод проверки достижения конца файла:

 

function SeekEof[ (var F: Text) ]: Boolean;

 

Отличие метода SeekEof() от обычного Eof() в том, что он умеет распознавать пустые строки (заполненные символами пробела) и исключать их из процесса чтения.

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

 

function EoLn[ (var F: Text) ]: Boolean;

 

Соответственно аналогичный метод, но игнорирующий пробелы:

function SeekEoLn[ (var F: Text) ]: Boolean;

 

Для лучшего понимания поведения методов SeekEof() и SeekEoLn() изучите предлагаемый код. В примере предложен способ хранения целых чисел в текстовом файле.

 

programDemoSeek;

{$APPTYPE CONSOLE}

uses SysUtils;

var f: TextFile;

i: Integer;

Begin

AssignFile(f,'c:\space.txt');

TRY

//создание демонстрационного файла

Rewrite(f);

Writeln(f, ' 1 2 3 4 5 '); // целые числа, разделенные пробелами

Writeln(f, ' '); // вносим пустые строки для демонстрации

// работы метода SeekEof

Writeln(f, ' ');

Writeln(f, ' 55 123 6 35 0 77');

//в файл записаны 4 строки

Reset(f); //переходим к операциям чтения

while SeekEof(f)=false do // цикл построчного чтения, игнорирующий

// пустые строки

Begin

while SeekEoln(f)=false do // цикл чтения чисел из строки,

// игнорирующий пробелы

Begin

Read(f, i);

Writeln(i);

end;

end;

FINALLY

CloseFile(f);

END;

Readln;

End.

Повторившие этот пример увидят, что, несмотря на наличие пустых строки пробелов между символами, программа выявила целые числа и вывела их на экран в один столбец.

 

Типизированные файлы

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

Допустим, что в файле необходимо хранить информацию о сотрудниках предприятия. Планируемый перечень данных включает фамилию, инициалы работника и его скромную зарплату – всего четыре позиции. В таком случае первый шаг разработчика – определить некоторую структуру данных (в терминах Pascal – записи), способную содержать информацию. Для этой цели опишем запись TPeople, затем при объявлении файловой переменной укажем, что она предназначена для работы с типизированным файлом типа

 

TPeople.

program ...;

{$APPTYPE CONSOLE}

usesSysUtils;

Type

TPeople = packed record

SurName : string[25];

FName,LName : CHAR;

Money : currency;

end;

varPeople: TPeople;

F : File Of TPeople;

Begin

AssignFile(F,'C:\People.dat'); {связывание файловой переменной с именем файла}

Rewrite(F); //создание файла

TRY

{заполнение полей записи}

withPeopledo

Begin

SurName:='Петров';

People.FName:='В';

People.LName:='А';

People.Money:=1000;

end;

Write(F,People); //внесение записи

FINALLY

CloseFile(F); //закрытие файла

END;

End.

Если в полях структуры используются строковые данные, то обязательно указывайте количество символов в строковом поле, иначе компилятор не сможет определить размер одной записи в файле.

Повторив пример создания типизированного файла, вы не увидели особых отличий от аналогичной операции формирования файла текстового типа. И в этом случае мы прошли через стандартную последовательность вызова методов AssignFile(), Rewrite(), Write() и Close(), подробно прокомментированных в предыдущем разделе.

Несколько иначе обстоят дела в случаях редактирования типизированного файла или чтения из него информации (см. алгоритм на рис. 4.2). Здесь нам может понадобиться системная переменная FileMode, определяющая режим открытия файла (по умолчанию ее значение равно 2, что предоставляет право для чтения и записи одновременно). Для открытия файла воспользуемся уже известным нам методом Reset() и… Настал черед завести знакомство с весьма полезной процедурой, отвечающей за позиционирование внутреннего указателя файла:

 

procedure Seek(var F; N: Longint);

 

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

 

Seek(F,FileSize(F)-1); // FileSize() возвращает количество записей

// в типизированном файле

 

При желании поместить указатель в начале файла надо набрать:

 

Seek(F,0);

 

а теперь переместим указатель ровно в середину файла:

 

Seek(F,FileSize(F) div 2);

 

На законный вопрос: «А как узнать, в каком месте находится указатель в настоящий момент?» нам ответит еще один метод:

 

function FilePos(var F): Longint;

 

И если указатель ссылается на начало файла, то функция вернет нулевое значение, а если на конец, то возвратит значение FileSize(). Следующий метод сообщает о количестве записей в типизированном файле:

 

functionFileSize(var F): Integer;

 

Очень важно помнить, что функция определения размера файла возвращает размер не в байтах, а в элементах, из которых состоит файл. В нашем случае элементом файла является запись TPeople, размер которой равен 36 байт. Поэтому, чтобы выяснить физический размер типизированного файла в байтах, необходимо умножить значение FileSize(F) (возвращающее количество записей в файле) на размер этой записи в байтах – SizeOf(TPeople).

То же утверждение в полной мере относится к методам FilePos() и Seek(): и у них в качестве единицы измерения выступает не байт, а номер элемента файла. Методы FileSize() и FilePos() не могут вызываться при работе с текстовыми файлами.

 

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

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

 

program ...;

{$APPTYPE CONSOLE}

uses SysUtils;

type TPeople = packed record

SurName : string[25];

FName, LName : CHAR;

Money : currency;

end;

var People : TPeople;

F : File Of TPeople;

Begin

AssignFile(F,'c:\People.dat');

FileMode:=1; Reset(F); //открываем файл в режиме «только для записи»

TRY

with People do

Begin

SurName:='Иванов';

People.FName:='С';

People.LName:='Г';

People.Money:=999.5;

end;

Seek(F,0); //позиционируем указатель на первый элемент файла

Write(F,People); //вносим изменения в файл

FINALLY

CloseFile(F);

END;

End.

Вот как должен выглядеть листинг программы, если вдруг потребуется удвоить оклады всем сотрудникам:

 

AssignFile(F,'c:\People.dat');

FileMode:=2;

Reset(F);

TRY

while FilePos(F)<>FileSize(F) do

Begin

Read(F,People);

People.Money:= People.Money*2;

Seek(F, FilePos(F)-1);

Write(F,People);

end;

FINALLY

CloseFile(F);

END;

Несколько сложнее обстоят дела с удалением из типизированного файла ненужного элемента. Однако при должной сноровке и эта задача вполне разрешима. Предположим, что за какую-то провинность (например, несанкционированное повышение зарплаты всем сотрудникам) из списка надо вычеркнуть бухгалтера Петрова. Для реализации этой карательной акции нам потребуются две файловые переменные: Source и Receptor. С помощью Source мы присоединимся к исходному файлу и просто переберем все его элементы, проверяя при этом, не является ли текущий элемент записью о нашалившем счетоводе Петрове. Вторая файловая переменная Receptor нацелена на создание файла-получателя. В этот файл мы переносим все элементы исходного файла, успешно избежавшие фильтрации (с фамилиями, отличными от «Петров»).

 

var People: TPeople;

Source, Receptor : File Of TPeople;

Begin

AssignFile(Source,'c:\People.dat');

TRY

Rename(Source, 'c:\~People.dat'); // переименовываем старый файл

Reset(Source); // подключаемся к старому файлу

AssignFile(Receptor,'c:\People.dat');

Rewrite(Receptor);

while FilePos(Source)<>FileSize(Source) do // цикл перебора элементов

// старого файла

Begin

Read(Source,People);

if People.SurName<>'Петров' thenWrite(Receptor,People);

end;

FINALLY

CloseFile(Source);

Erase(Source); //удаляем старый файл

CloseFile(Receptor);

END;

End.

В рассмотренном листинге мы использовали еще две новые процедуры. Первая из них предназначена для переименования файла F. Новое имя надо указать в параметре NewName.

 

procedure Rename(var F; Newname: string);

 

Для удаления файла нам понадобится метод Erase().

 

procedure Erase(var F);

 

Прежде чем удалить файл, определенный в файловой переменной F, обязательно закройте его, вызвав метод CloseFile().

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

В таких случаях программисты идут на хитрость: в состав полей записи типизированного файла добавляется еще одно поле логического типа, например Deleted, в котором хранится признак удаления. При поступлении команды на удаление записи вместо реального стирания строки полю Deleted присваивается значение true и запись убирается с экрана. На все это уходят тысячные доли секунды, а реальное удаление строк выполняется (в тайне от всех) в момент закрытия программы.

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

Двоичные файлы

Подавляющее большинство файлов, хранящихся на жестких дисках компьютеров, не являются типизированными. С точки зрения языка Pascal они не имеют строгого формата. Точнее говоря, в их формате разбираются только узкоспециальные программы. Например, программы Adobe PageMaker и Microsoft Word (по сути выполняющие аналогичные задачи – редактирование текста) работают с весьма несхожими файлами. Дело в том, что в их файлах помимо текста хранится обширная служебная информация: гарнитура, кегль и цвет шрифта, трекинг, интерлиньяж и многое другое. Разработчики двух разных программных продуктов для каждого из пакетов сформировали свой формат файла и вполне довольны полученными результатами. Возможно, и вы создадите свой тип файла, однако в нашей классификации он все равно будет именоваться двоичным.

Отличие состава базовых операций ввода-вывода, применяемых для нетипизированных файлов, заключается в методах, осуществляющих чтение и запись. Суть отличия от уже известных вам процедур Read() и Write() заключается в том, что чтение и запись производятся блоками.

 

procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred:

Integer]);

procedure BlockWrite(var F: File; var Buf; Count: Integer [; var AmtTransferred:

Integer]);

 

С первым аргументом F все ясно: это файловая переменная. Второй аргумент Buf служит буфером, в который записываются прочитанные данные или, наоборот, из которой считывается информация для сохранения в файле. Параметр Count определяет, сколько записей планируется считать из файла за одну операцию чтения. Как правило, ему присваивают значение, равное 1. Последний аргумент, AmtTransferred, необязательный. Во время операций чтения из него можно узнать количество записей, реально считанных из файла, а во время осуществления записи в переменной окажется количество записей, внесенных в файл.

Внимательный читатель наверняка уже заинтересовался: «Что мы имеем в виду, когда применяем термин запись при работе с нетипизированным файлом?» Действительно, здесь требуется внести некоторую ясность. Еще раз взгляните на объявление методов BlockRead() и BlockWrite(). В этих процедурах не определен тип аргумента Buf, что так не похоже на строгий язык Pascal. Но каким образом можно определить тип данных для работы с файлом, который по определению нетипизирован? Поэтому в качестве буфера обычно используют обычный байтовый массив:

 

var Buf : array [0..127] of byte;

 

Размер массива устанавливается программистом. Вот про этот размер и говорят – размер записи, а сам буфер часто именуют записью. По умолчанию размер буфера назначают равным 128 байт. Если этот размер не устраивает, его можно изменить под свою задачу. Однако при этом надо помнить о втором параметре процедур Reset() и Rewrite(), который определяет размер блока. Если есть необходимость, перечитайте раздел, посвященный текстовым файлам.

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

 

program DemoBinFl;

{$APPTYPE CONSOLE}

uses SysUtils;

varF : File;

Buffer : array[0..1023] of byte;

I : integer;

Begin

for i:=0 to SizeOf(Buffer) do Buffer[i]:=RANDOM(255);

AssignFile(F,'c:\binfile.dat');

TRY

Rewrite(F,1024);

BlockWrite(F,Buffer,1);

FINALLY

CloseFile(F);

END;

End.

В качестве буфера используется массив размером 1 Кбайт. Заполнив массив псевдослучайными числами, переносим содержимое его ячеек на жесткий диск.

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

 

program DemoBinRead;

{$APPTYPE CONSOLE}

{Чтение из двоичного файла}

usesSysUtils;

varF : File;

Buffer: array[0..255] of byte;

i : integer;

Begin

AssignFile(F,'c:\binfile.dat');

TRY

Reset(F,256);

while EOF(F)=False do

Begin

BlockRead(F,Buffer,1);

//прочие операции

end;

FINALLY

CloseFile(F);

END;

End.

Begin

AssignFile(F,'c:\test.tst');

TRY

Reset(f,255);

BlockRead(F,Buffer,1);

FINALLY

CloseFile(f);

END;

End.

Однако вполне работоспособный код откажется работать, если в корне диска C:\ не окажется тестового файла с названием test.tst. В таком случае на экране компьютера появится сообщение, информирующее нас о таком прискорбном факте. А теперь применим наши знания относительно обработки ошибок ввода-вывода и напишем более жизнестойкие строки.

 

 

var F : File;

Buffer : array[0..255] of byte;

Begin

AssignFile(F,'c:\test.tst');

{$I-}

Reset(f,255);

ifIOResult=0 then

Begin

BlockRead(F,Buffer,1);

CloseFile(f);

End

elseWriteLn('Sorry! File not found!');

{$I+}

ReadLn;

End.

Type

POverlapped = ^TOverlapped;

_OVERLAPPED = record

Internal: DWORD;

InternalHigh: DWORD;

Offset: DWORD;

OffsetHigh: DWORD;

hEvent: THandle;

end;

Поля Internal и InternalHigh зарезервированы за операционной системой. В частности, они используются ОС для определения статуса проводимой асинхронной операции. Поля Offset и OffsetHigh соответственно хранят 32 младших и 32 старших бита позиции файла, с которой должно начинаться чтение или запись. Поле hEvent содержит дескриптор специального объекта синхронизации, называемого событием. Его мы изучим в главе 20 «Процессы и потоки в среде Windows». В данном контексте объект-событие применяется для передачи информации процедуре завершения. Последний параметр методов ReadFileEx() и WriteFileEx() – указатель на функцию обратного вызова FileIOCompletionRoutine(). Эта функция вызывается при завершении или отмене операции ввода-вывода.

 

Begin

pExName:=PChar('C:\1.txt');

pNewName:=PChar('С:\Copy_Of_1.txt');

CopyFile(pExName,pNewName,true);

end;

 

Для перемещения файлов используйте функцию MoveFile():

 

Function MoveFile(ExistingFileName, NewFileName : PChar) : Boolean;

 

Параметры функции MoveFile() идентичны первым двум аргументам метода CopyFile(). В случае успеха функция возвращает ненулевое значение.

 

Имя файла и путь к нему

Широкий спектр методов нацелен на организацию работы с именем файла, его расширением и путем к файлу. Почти все рассматриваемые методы объявлены в модуле SysUtils.

 

Таблица 4.4. Функции для работы с именем файла

Функция Описание
function ChangeFileExt(const FileName, Extension: string): string;     function ExcludeTrailingBackslash( const S: string): string;   function IncludeTrailingBackslash( const S: string): string; function ExpandFileName(const File- Name: string): string; function ExpandUNCFileName(const FileName: string): string; function ExtractFileDir(const FileName: string): string;   function ExtractFilePath(const File- Name: string): string; function ExtractFileDrive(const FileName: string): string; function ExtractFileExt(const FileName: string): string;   function ExtractFileName(const FileName: string): string; function ExtractRelativePath(const BaseName, DestName: string): string;     functionExtractShortPathName(const FileName: string): string; function IsPathDelimiter(const S: string; Index: Integer): Boolean; function MatchesMask(const Filename, Mask: string): Boolean; procedure ProcessPath(const EditText: string; var Drive: Char; var DirPart: string; var FilePart: string); function MinimizeName(const Filename: TFileName; Canvas: TCanvas; MaxLen: Integer): TFileName; Изменяет расширение в имени файла FileName на новое, определенное параметром Extension. Возвращает новое значение имени.   Удаляет последнюю наклонную черту (слэш) в пути S.   Завершает путь S наклонной чертой.   Преобразует имя файла FileName в полный путь к файлу, включая имена файла и диска. Преобразует имя файла в полный путь к сетевомуфайлу:\\<Имя_сервера>\<Ресурс>   Извлекает из строки с полным именем файла путь к каталогу файла и имя каталога.   Извлекает из строки с полным именем файла путь к файлу, заканчивающийся слэшем. Извлекает из строки с полным именем файла имя диска, завершающееся двоеточием. Возвращает расширение в имени файла. В результирующую строку входит разделительная точка и непосредственно расширение. Извлекает из строки с полным именем файла имя и расширение файла. Возвращает относительный путь к файлу. Здесь DestName – полный путь, BaseName – путь, вычитаемый из полного пути. FileName:='C:\Windows\System\comctrl32.dll'; Result:=ExtractRelativePath('c:\windows\',FileName); В результате будет получена строка System\comctrl32.dll Конвертирует полный путь к файлу в укороченный формат 8.3.   Проверяет наличие в позиции Index символа наклонной черты влево. Проверяет соответствие имени файла шаблону маски. Функция определена в модуле Masks. Разделяет полный путь к файлу на составные части: имя диска, путь к каталогу, имя файла. Функция определена в модуле FileCtrl.   Функция определена в модуле FileCtrl. Метод применяется совместно с элементами управления, обладающими канвой Canvas (поверхностью для рисования). Задача метода – поместить имя файла FileName в области, ограниченной по ширине MaxLen пикселами. В соответствии с ограничениями функция минимизирует имя файла.

Дата и время создания файла

Самый простой способ знакомства с возрастом файла заключается в использовании функции:

 

function FileAge(const FileName: string): Integer;

 

Как видите, для этого достаточно передать путь и имя файла в параметр FileName. Еще один метод, выполняющий аналогичную задачу:

 

function FileGetDate (Handle: Integer): Integer;

 

Однако здесь вместо имени требуется указатель на файл. Другими словами, последний должен быть открыт методом FileOpen() или создан методом FileCreate(). Оба метода возвращают дату не в привычном для нас формате TDateTime, а в виде структуры даты-времени, принятой в Windows. Поэтому для приведения результата к понятному для языка Pascal виду применяется метод:

 

function FileDateToDateTime(FileDate: Integer): TDateTime;

 

Существует функция, решающая и обратную задачу:

 

function DateTimeToFileDate(DateTime: TDateTime): Integer;

varFileName : string;

Age : INTEGER;

Begin

FileName:='C:\Autoexec.bat';

Age:=FileAge(FileName);

WriteLn(Age);

WriteLn(DateTimeToStr(FileDateToDateTime(Age)));

ReadLn;

End.

Для того чтобы назначить файлу новое время и дату, понадобится метод:

 

function FileSetDate(Handle: Integer; Age: Integer): Integer;

var FileName : string;

F,Age : INTEGER;

. . .

Age:=DateTimeToFileDate(Now);

F:=FileOpen(FileName, fmOpenReadWrite);

FileSetDate(F, Age);

FileClose(F);

 

Атрибуты файла

В языке Pascal объявлено два метода для работы с атрибутами файла. За чтение и установку атрибутов отвечают соответственно методы:

 

function FileGetAttr(const FileName: string): Integer;

functionFileSetAttr(const FileName: string; Attr: Integer): Integer;

 

В обеих функциях FileName – имя файла. Во втором методе назначение атрибутов выполняется с помощью параметра Attr. Существующие типы атрибутов представлены в табл. 4.5.

 

Таблица 4.5. Атрибуты файла

Атрибут Значение Описание
faReadOnly faHidden faSysFile faVolumeID faDirectory faArchive faAnyFile $00000001 $00000002 $00000004 $00000008 $00000010 $00000020 $0000003F Только для чтения Скрытый Системный Диск Каталог Архив Любой

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

 

function DeleteMyFile(FileName : string) : Boolean;

var Attr : INTEGER;

Begin

Attr:=FileGetAttr(FileName);

if (Attr and faHidden = 0) or (Attr and faSysFile =

Последнее изменение этой страницы: 2016-03-17; Просмотров: 19; Нарушение авторского права страницы


lektsia.com 2007 - 2017 год. Все права принадлежат их авторам! (0.164 с.) Главная | Обратная связь