Глава 11
ФАЙЛЫ
Под файлом понимается именованная область внешней памяти ПК (жесткого диска, гибкой дискеты, диска CD-ROM).
Любой файл имеет три характерные особенности. Во-первых, у него есть имя, что дает возможность программе работать одновременно с несколькими файлами. Во-вторых, он содержит компоненты одного типа. Типом компонентов может быть любой тип Object Pascal, кроме файлов. Иными словами, нельзя создать “файл файлов”. В-третьих, длина вновь создаваемого файла никак не оговаривается при его объявлении и ограничивается только емкостью устройств внешней памяти.
Файловый тип можно задать одним из трех способов:
<имя> = File of <тип>;
<имя> = TextFile;
<имя> = File;
Здесь <имя> - имя файлового типа (правильный идентификатор);
File, of - зарезервированные слова (файл, из); TextFile - имя стандартного типа текстовых файлов; <тип> - любой тип Object Pascal, кроме файлов. Например:
type
Product = record Name: String; Code : Word;
Cost: Comp
end;
TextSO = File of String[80];
var
Fl: File of Char;
F2: TextFile;
F3: File;
F4: TextSO;
F5: File of Product;
В зависимости от способа объявления можно выделить три вида файлов:
В наших примерах f1, F4 и F5 - типизированные файлы, F2 - текстовый файл, F3 - нетипизированный файл. Вид файла, вообще говоря, определяет способ хранения в нем информации. Однако в Object Pascal нет средств контроля вида ранее созданных файлов. При объявлении уже существующих файлов программист должен сам следить за соответствием вида объявления характеру хранящихся в файле данных.
11.1. ДОСТУП К ФАЙЛАМ
Файлы становятся доступны программе только после выполнения особой процедуры открытия файла. Эта процедура заключается в связывании ранее объявленной файловой переменной с именем существующего или вновь создаваемого файла, а также в указании направления обмена информацией: чтение из файла или запись в него.
Файловая переменная связывается с именем файла в результате обращения к стандартной процедуре AssignFile :
AssignFile (<ф.п.>, <имя файла>);
Здесь <ф.п.> - файловая переменная (правильный идентификатор, объявленный в программе как переменная файлового типа);
<имя файла > - текстовое выражение, содержащее имя файла и, если это необходимо, маршрут доступа к нему.
Инициировать файл означает указать для этого файла направление передачи данных. В Object Pascal можно открыть файл для чтения, для записи информации, а также для чтения и записи одновременно.
Для чтения файл инициируется с помощью стандартной процедуры Reset;
Reset (<ф.п.>);
Здесь <ф. п. > - файловая переменная, связанная ранее процедурой AssignFile с уже существующим файлом.
При выполнении этой процедуры дисковый файл подготавливается к чтению информации. В результате специальная переменная-указатель, связанная с этим файлом, будет указывать на начало файла, т. е. на компонент с порядковым номером 0.
Если делается попытка иницийровать чтение из несуществующего файла, возбуждается исключительная ситуация.
Чтобы проверить, существует ли дисковый файл, можно использовать стандартную функцию FileExists, которая возвращает Truе, если указанный при обращении этой функции файл существует, и False - если не существует.
Например
begin
if FileExists(FileName) then
..... // Файл существует
else ..... // Файл не существует
end;
В Object Pascal разрешается обращаться к типизированным файлам, открытым процедурой Reset (т. е. для чтения информации), с помощью процедуры write (т. е. для записи информации). Такая возможность позволяет легко обновлять ранее созданные типизированные файлы и при необходимости расширять их. Для текстовых файлов, открытых процедурой Reset, нельзя использовать процедуру Write ИЛИ WriteLn.
Стандартная процедура
Rewrite (<ф.п.>);
инициирует запись информации в файл, связанный с файловой переменной <ф.п.>. Процедурой Rewrite нельзя инициировать запись информации в ранее существовавший дисковый файл: при выполнении этой процедуры старый файл (если он был) уничтожается и никаких сообщений об этом в программу не передается. Новый файл подготавливается к приему информации, и его указатель принимает значение 0.
Стандартная процедура
Append (<ф.п.>)
инициирует запись в ранее существовавший текстовый файл для его расширения, при этом указатель файла устанавливается в его конец. Процедура Append применима только к текстовым файлам, т. е. их файловая переменная должна иметь тип TextFile (см. выше). Процедурой Append нельзя инициировать запись в типизированный или нетипизированный файл. Если текстовый файл ранее уже был открыт с помощью Reset или Rewrite, использование процедуры Арpend приведет к закрытию этого файла и открытию его вновь, но уже для добавления записей.
11.2. ПРОЦЕДУРЫ И ФУНКЦИИ ДЛЯ РАБОТЫ С ФАЙЛАМИ
Ниже описываются процедуры и функции, которые можно использовать с файлами любого вида. Специфика работы с типизированными, текстовыми и нетипизированными файлами рассматривается в следующих разделах.
Таблица 11.1. Подпрограммы для работы с файлами
Procedure AssignFile(var F; FileName: String) ; |
Связывает файловую переменную f с именем файла FileName |
|
function ChangeFileExt (const FileName, Ex tension: String): Strings; |
Изменяет существующее расширение файла на расширение, заданное параметром Extension
|
|
Procedure ChDir(Path: String);
|
Изменяет текущий каталог: path - строковое выражение, содержащее путь к устанавливаемому по умолчаниюкаталогу |
|
Procedure CloseFile (var F) ;
|
Закрывает файл, однако связь файловой переменной F с именем файла, установленная ранее процедурой Assign-File, сохраняется. При создании нового или расширении старого файла процедура обеспечивает сохранение в файле всех новых записей и регистрацию файла в каталоге. Функции процедуры CloseFile выполняются автоматически по отношению ко всем открытым файлам при нормальном завершении программы. Поскольку связь файла с файловой переменной сохраняется, файл можно повторно открыть без дополнительного использования Процедуры AssignFile |
|
function DateTime-ToFileDate(DateTime: TDateTime): Integer; |
Преобразует значение DateTime в системный формат времени создания (обновления) файла |
|
Function DiskFree(D: Byte): Longint;
|
Возвращает объем в байтах свободного пространства на указанном диске: D - номер диска (0 - устройство по умолчанию, 1 - диск А ,2- диск В и т, д.). Функция возвращает значение -1, если указан номер несуществующего диска |
|
function Delete- File(const FileName: String): Boolean; |
Уничтожает файл с именем (и, возможно, маршрутом доступа) FileName. Возвращает True, если операция прошла успешно |
|
Function DiskSize(D: Byte) : Longing;
|
Возвращает объем в байтах полного пространства на указанном диске: d - номер диска (0 - устройство по умолчанию, 1 - диск А ,2- диск Д и т. д.). Функция возвращает значение -1, если указан номер несуществующего диска |
|
Function EOF (var F) : Boolean; |
Тестирует конец файла и возвращает True, если файловый указатель стоит в конце файла. При записи это означает, что очередной компонент будет добавлен в конец файла, при чтении - что файл исчерпан |
Procedure Erase(var F); |
Уничтожает файл f. Перед выполнением процедуры не обходимо закрыть файл (см. замечание ниже) |
|
function FileAge(const FileName: String): In teger; |
Для файла FileName возвращает время его последнего обновления (в системном формате) или -1, если такого файла не существует |
|
function ExcludeTrai- lingBackslash(const S: String): Strings; |
Исключает из строки s замыкающий символ “\” (если этот символ не замыкает строку, возвращает S без изменения) |
|
function ExpandUNC- FileName(const File Name: String): String; |
Дополняет имя файла текущим сетевым каталогом (и диском) |
|
function ExtractFile- Dir(const FileName: String): Strings; |
Извлекает из полного имени файла маршрут доступа к нему (без последнего символа “\”) |
|
function ExtractFileExt(const FileName: String): Strings; |
Извлекает из полного имени файла его расширение (с ведущей точкой) |
|
function ExtractFileName(cons t Fi1eName: String): Strings; |
Извлекает из полного имени файла его имя (с расширением) |
|
function ExtractFilePath(const File Name: String): Strings; |
Извлекает из полного имени файла маршрут доступа к нему (с последним символом “\”) |
|
function ExtractRela- tivePath(const Bas eName, De s tName: String): Strings; |
Извлекает из полного имени файла имя маршрута относительно DestName (промежуточные каталоги заменяются символами “..\”) |
|
function ExtractShort- PathName(const File Name : String): Strings; |
Преобразует имя файла к короткому формату 8.3 для MSDOS и Windows 3-х |
|
function FileDateTo- DateTime(FileDate: Integer) : TDateTime; |
Преобразует системный формат FileDate времени создания файла в формат дата-время
|
|
Function FileEx- ists(const FileName: String): Boolean; |
Возвращает True, если файл с именем (и, возможно, маршрутом доступа) FileName существует |
|
function File Get Date (Handle: Integer): Integers; |
По заданному дескриптору файла Handle возвращает время и дату его создания в системном формате. Возвращает 0 в случае успеха или код ошибки |
|
function FileSetDate (Handle: Integer; Age: Integer): Integers; |
Для файла с дескриптором Handle устанавливает новое время и дату его создания Age в системном формате. Возвращает 0 в случае успеха или код ошибки |
|
Function FindFirst (const Path: String; Attr: Integer; var F: TSearchRec): Integer;
|
Возвращает атрибуты первого из файлов, зарегистрированных в указанном каталоге: Path - маршрут поиска и маска выбора файлов; Attr - атрибуты выбираемых файлов; f - переменная типа TSesrchRec, в которой будет возвращено имя первого выбранного файла. При успешном поиске возвращает значение 0 |
|
Procedure Find- Close(var F: TSear- chRec); |
Освобождает память, выделенную для поиска файлов функциями FindFirst/FindNext |
|
Function FindNext(var F: TSearchRec): Integer; |
Возвращает в переменой f имя следующего файла в каталоге. Переменная f должна предварительно инициироваться обращением к функции FindFirst. При успешном поиске возвращает значение 0 |
|
Procedure Flush(varF); |
Очищает внутренний буфер файла и, таким образом, гарантирует сохранность всех последних изменений файла на диске |
|
Procedure GetDir(D: Byte; var S: Strings-
|
Возвращает имя текущего каталога (каталога по умолчанию): d - номер устройства (0 - устройство по умолчанию, 1 - диск А, 2- диск В и т. д.); s - переменная типа String, в которой возвращается путь к текущему каталогу на указанном диске |
|
function IncludeTrailingBackslash(const S:String): String; |
Возвращает полный маршрут доступа к файлу с ведомым символом “\” |
|
Function lOResult: In teger; |
Возвращает условный признак последней операции ввода-вывода |
|
function IsPathDelimiter(const S: String; Index: Integer): Boo lean; |
Возвращает True, если в строке S символ index есть “\”.
|
|
function MatchesMask (const Filename, Mask: String): Boolean; |
Возвращает True, если имя FileName соответствует групповому имени Mask |
|
Procedure MkDir(Dir: String) ;
|
Создает новый каталог на указанном диске: Dir маршрут поиска каталога. Последним именем в маршруте, т.е. именем вновь создаваемого каталога, не может быть имя уже существующего каталога |
|
procedure ProcessPath (const EditText: String; var Drive: Char; var DirPart: String; var FilePart: String) ; |
Возвращает имя диска, маршрут поиска и имя файла в переменных Drive, DirPart и FilePart соответственно. EditText - полное имя файла
|
|
Procedure Rename(var F; NewName: String); |
Переименовывает файл F; NewName -' строковое выражение, содержащее новое имя файла. Перед выполнением процедуры необходимо закрыть файл только для не типизированных файлов и указывает раз мер блока данных |
|
Procedure Resetfvar F: File; RecSize: Word]); |
Открывает существующий файл. RecSize имеет смысл |
|
Procedure Rewrite(varFile [; Recsize: ,Word]) ; |
Создает новый файл. Recsize имеет смысл только для не типизированных файлов и указывает размер блока данных |
|
Procedure RmDir(Dir:String);
|
Удаляет каталог Dir. Удаляемый каталог должен быть пустым, т. е. не содержать файлов или имен каталогов нижнего уровня |
С каждой файловой переменой в момент открытия файла связывается структура данных, которая в числе прочих содержит поле Handle - системный дескриптор файла. Это поле следует использовать При обращении К функциям FileGetTime и FileSetTime. Эти функции, а также функция FileAge и поле Time записи TsearchRec (см. ниже) оперируют системным форматом времени-даты, который можно перевести в стандартный тип дата-время с помощью функции FileDateToDateTime (функция DateTimeToFileDate Осуществляет обратное преобразование).
Подпрограммы FindFirst, FindNext И FindClose позволяют получить
доступ к группе файлов, объединенных общими признаками. Эти признаки при обращении к функции FindFirst указываются маской выбора файлов и их атрибутами.
При формировании маски выбора файлов могут использоваться следующие символы-заменители:
* означает, что на месте этого символа может стоять сколько угодно (в том числе ноль) разрешенных символов имени или расширения файла;
? означает, что на месте этого символа может стоять один из разрешенных символов.
Например:
* . * выбирает все файлы из каталога;
с* . * выбирает все файлы с именами, начинающимися на с
fcl.pas, ccl2345, с.dat И Т.Д.);
а? . dat выбирает имена файлов типа ао. dat, az. dat и т. д.
Маске выбора может предшествовать маршрут поиска файлов. Например, команда
C:\Dir\SubDir\*.pas
означает выбирать все файлы с расширением .раз из каталога Sub-Dir, находящегося на диске с; каталог subDir зарегистрирован в каталоге верхнего уровня Dir, который, в свою очередь, входит в корневой каталог. Если маршрут не указан, файлы ищутся в текущем каталоге.
Параметр Attr при обращении к FindFirst содержит двоичные разряды (биты), уточняющие, к каким именно файлам разрешен доступ. Вот как объявляются файловые атрибуты в модуле SysUtils:
const
faReadOnly = $01; // Только чтение
faHidden = $02; // Скрытый файл
faSysFile = $04; // Системный файл
faVolumeID = $08; // Идентификатор тома
faDirectory = $10; // Имя подкаталога
faArchive = $20; // Архивный файл
faAnyFile = $3F; // Любой файл
Комбинацией бит в этом байте можно указывать самые разные варианты, например $06 - выбирать все скрытые и/или системные файлы.
Результат работы процедуры FindFirst возвращается в переменной типа TSearchRec. Этот тип определяется следующим образом:
type
TSearchRec = record
Time : Integer;
Size : Integer;
Attr : Integer;
Name : TFileName;
ExcludeAttr: Integer;
FindHandle : THandle;
FindDate : Twin32FindDate;
end;
Здесь Attr - атрибуты файла (см. выше); Time - время и дата создания или последнего обновления файла в системном формате; size - длина файла в байтах; Name - имя и расширение файла; FindDate -содержит дополнительную информацию о файле (время создания, время последнего доступа).
Результат обращения к процедуре FindFist возвращается в значении типа integer, которое равно 0, если нет ошибок.
Следующая простая программа иллюстрирует способ использования функций PindFirst И FindNext. Программа выводит в окно
многострочного редактора mmoutput список всех файлов, маска выбора которых (и, возможно, маршрут поиска) указана в окне edInput:
procedure TfmExample.bbRunClick(Sender: TObject);
var
Mask: String;
SR: TSearchRec;
begin
Mask := edInput.Text;
if Mask = '' then Mask := '*.*';
mmOutput.Lines.Clear;
if FindFirst(Mask,faAnyFile,SR)=0 then
repeat
mmOutput.Lines.Add(SR.Name);
until FindNext(SR)<>0
FindClose(SR);
end;
Любое обращение к файлу в Object Pascal осуществляется через некоторый буфер, что необходимо для согласования внутреннего представления файлового компонента (записи) с принятым в ОС форматом хранения данных на диске. В ходе выполнения процедуры Flush все новые записи будут действительно записаны на диск. Процедура игнорируется, если файл был инициирован для чтения процедурой Reset.
Функция IOResuit досталась Object Pascal в наследство от Турбо Паскаля. Она используется следующим образом: перед фрагментом программы, в котором может возникнуть ошибка ввода/вывода, ставится директива {$!-}, отключающая автоконтроль операций ввода/вывода. После выполнения опасного участка автоконтроль включается вновь директивой {$!+} и вызывается функция IOResuit. Если операция завершилась успешно, функция возвращает ноль. Следует помнить, что IOResuit становится доступной только при отключенном автоконтроле ошибок ввода/вывода. Если автоконтроль отключен, а операция ввода-вывода привела к возникновению ошибки, устанавливается флаг ошибки и все последующие обращения к вводу/выводу блокируются, пока не будет вызвана функция IOResuit. Вот как можно проверить существование файла с использованием функции IOResuit:
var
F: File;
begin
AssignFile(F,'MyFile') ;
{$!-} // Отключаем автоконтроль Reset(F);
// Пытаемся открыть файл
{$!+} // Включаем автоконтроль
if IOResult=0 then
// Файл существует
else
// Файл не существует
end;
В Object Pascal для защиты программы от краха при выполнении потенциально опасных фрагментов широко используется механизм обработки исключительных ситуаций. Следующий фрагмент показывает, как можно использовать этот механизм при работе с файлами. Предположим, что требуется отредактировать файл, имя которого содержит переменную Name. Перед редактированием необходимо убедиться, что нужный файл имеется на диске, и создать его страховочную копию с расширением вак. Если одноименный файл (т. е. с таким же именем и расширением вак) уже существует, его надо стереть.
var
Fi : TextFile; // Исходный файл
Fo : TextFile; // Отредактированный файл
Name : String;
Name_bak : String;
const
ВАК = '.bak';
begin
// Получаем в name_bak имя файла с расширением .ВАК:
Name_bak := Name - ExtractFileExt(Name) + ВАК;
// Проверяем существование исходного файла:
AssignFile(Fi,Name) ;
try
Reset(Fi) ;
except
Halt; // Завершаем программу: файла не существует
end;
CloseFile(Fi) ;
// Проверяем существование .ВАК-файла:
AssignFile(Fo,Name_bak) ;
try
Reset(Fo) ;
// Файл .ВАК существует:
CloseFile(Fo); // Закрываем его Erase(Fo)
// и уничтожаем
except
end;
// Проверки закончены, подготовка к работе:
Rename(Fi,Name__bak) ;
Reset-(Fi) ;
AssignFile(Fo,Name);
Rewrite(Fo);
end.
Обратите внимание: проверка на существование файла вак в данном примере необходима, так как обращение
Rename(Fi,Name_bak);
вызовет ошибку в случае, если такой файл существует.
11.3. ТЕКСТОВЫЕ ФАЙЛЫ
Текстовые файлы связываются с файловыми переменными, принадлежащими к стандартному типу TextFiie. Текстовые файлы предназначены для хранения текстовой информации. Именно в такого типа файлах хранятся, например, исходные тексты программ. Компоненты (записи) текстового файла могут иметь переменную длину, что существенно влияет на характер работы с ними.
Текстовый файл трактуется в Object Pascal как совокупность строк переменной длины. Доступ к каждой строке возможен лишь последовательно, начиная с первой. При создании текстового файла в конце каждой строки ставится специальный признак eoln (End Of LiNe - конец строки), а в конце всего файла - признак eof (End Of File - конец файла). Эти признаки можно протестировать одноименными логическими функциями (см. ниже). При формировании текстовых файлов используются следующие системные соглашения:
eoln - последовательность кодов #13 (cr) и #10 (lf);
EOF -КОД #26.
В Delphi 6 при создании межплатформенных приложений признаком конца строки считается один символ LF(#10)
Для доступа к записям применяются процедуры Read, ReadLn, write, writebn. Они отличаются возможностью обращения к ним с переменным числом фактических параметров, в качестве которых могут использоваться символы, строки и числа. Первым параметром в любой из перечисленных процедур должна стоять файловая
переменная. Обращение осуществляется к дисковому файлу, связанному С Переменной Процедурой AssignFile.
Таблица 11.2. Подпрограммы для работы с текстовыми файлами
Function Eoln(var F: TextFile): Boolean; |
Тестирует маркер конца строки и возвращает True, если конец строки достигнут |
Procedure Read(var F: TextFile; V1 [, V2,...,Vn ]); |
Читает из текстового файла последовательность символьных представлении переменных Vi типа char. String, а также любого целого или вещественного типа, игнорируя признаки EOLN |
Procedure ReadLn (var F: TextFile; [VI [, V2, ...,Vn]]); |
Читает из текстового файла последовательность символьных представлении переменных Vi типа char, String, а также любого целого или вещественного типа с учетом границ строк |
Function SeekEof(var F: Text): Boolean; |
Пропускает все пробелы, знаки табуляции и маркеры конца строки eoln до маркера конца файла eof или до первого значащего символа и возвращает True, если маркер eof обнаружен |
Function SeekEoln (var F: TextFile): Boolean; |
Пропускает все пробелы и знаки табуляции до маркера конца строки eoln или до первого значащего символа и возвращает True, если маркер обнаружен |
Procedure Write(var F: Text; PI [, P2,..., Pn] ) ; |
Записывает символьные представления параметров Pi в текстовый файл |
Procedure WriteLn (var F: Text; [PI [, P2, ..., Pn]]); |
Записывает символьные представления параметров Pi и при знак конца строки eoln в текстовый файл |
Процедура Read предназначена для последовательного чтения из текстового файла символьных представлений переменных Vi. При чтении переменных типа char выполняется чтение одного символа и присваивание считанного значения переменной. Если перед выполнением чтения указатель файла достиг конца очередной строки, то результатом чтения будет символ cr (код #1з), а если достигнут конец файла, то символ eof (код #26). Процедуру Read не рекомендуется использовать для ввода переменных типа string, т. к. она не способна “перепрыгнуть” через разделитель строк eoln и читает только первую строку текстового файла. Для ввода последовательности строк нужно использовать процедуру ReadLn (см. ниже).
Следующая программа “зависнет”, т. к. никогда не будет прочитана вторая строка файла:
procedure TfmExample.bbRunClick(Sender: TObject);
var
F: TextFile;
S: String;
begin
AssignFile(F,'example.pas');
Reset(F);
while- not EOF(F) do
begin
Read(P,S); // Ошибка! Бесконечный цикл!
mmOutput.Lines.Add(S)
end;
CloseFile(F)
end;
При вводе численных переменных процедура Read вначале выделяет подстроку во входном потоке по следующему правилу: все ведущие пробелы, символы табуляции и маркеры конца строк eoln пропускаются; после выделения первого значащего символа, наоборот, любой из перечисленных символов или символ eof служат признаком конца подстроки. Выделенная таким образом подстрока затем рассматривается как символьное представление числовой константы соответствующего типа и преобразуется во внутреннее представление, а полученное значение присваивается переменной. Если в подстроке был нарушен требуемый формат представления числовой константы, возникает исключительная ситуация. Если при пропуске ведущих пробелов встретился символ eof, переменная получает значение 0. В Object Pascal не предусмотрен ввод шестнадцатеричных констант.
Процедура Read прекрасно приспособлена к вводу чисел. При обращении к ней за вводом очередного целого или вещественного числа процедура “перескакивает” маркеры конца строк, т. е. фактически весь файл рассматривается ею как одна длинная строка, содержащая текстовые представления чисел. В сочетании с проверкой конца файла функцией eof процедура Read позволяет организовать простой ввод массивов данных, например, так:
const
N = 1000; // Максимальная длина ввода
var
F : TextFile;
М : array [1..N] of Real;
i : Integer;
begin
AssignFile(F, 'prog.dat');
Reset(F);
i := 1;
while not EOF(f) and (i <= N) do
begin
Read(F, M[i]);
inc (i) end;
CloseFile(F) ;
end.
Процедура ReadLn идентична процедуре Read за исключением того, что после считывания последней переменной оставшаяся часть строки до маркера eoln пропускается, поэтому следующее обращение к ReadLn начинается с первого символа новой строки. Кроме того, эту процедуру можно вызвать без параметров vi„ что приведет к пропуску всех символов текущей строки вплоть до eoln. Если в обработчике bbRunClick (см. пример на предыдущей странице) заменить Read на ReadLn, программа выведет в окно компонента mmout-put все строки из текстового файла example . раs.
Процедура write обеспечивает вывод в текстовый файл группы переменных. Любой элемент списка вывода может иметь форму
OutExpr [ : MinWidth [ : DecPlaces ] ]
Здесь OutExpr - выводимое выражение; MinWidth, DecPlaces - выражения типа integer (квадратные скобки означают возможность отсутствия заключенных в них параметров). Параметр MinWidth, если он присутствует, указывает минимальную ширину поля, в которое будет записываться символьное представление значения OutExpr. Если символьное представление имеет меньшую длину, чем MinWidth, оно будет дополнено слева пробелами, если большую длину, то MinWidth игнорируется и в файл помещается необходимое число символов. Параметр DecPlaces задает количество десятичных знаков в дробной части вещественного числа. Он может использоваться только совместно с MinWidth и только по отношению к выводимому выражению одного из вещественных типов.
Если ширина поля вывода не указана, соответствующий параметр выводится вслед за предыдущим без какого-либо их разделения. Символы и строки передаются выводному файлу без изменений, но снабжаются ведущими пробелами, если задана ширина поля вывода и эта ширина больше требуемой для вывода.
При выводе логических выражений в зависимости от их значения выводятся слова True или False. (Ввод логических констант процедурами Read или ReadLn не предусмотрен.)
Вещественные числа выводятся в экспоненциальном формате, если не указан параметр Decpiaces, в противном случае выбирается формат представления числа с фиксированной точкой. Если подпараметр MinWidth опущен, принимается его значение по умолчанию (23). Если Minwidth меньше 10, считается, что он равен 10. Если подпараметр Decpiaces равен нулю, ни дробная часть числа, ни десятичная точка не выводятся. При отрицательном значении Decpiaces этот параметр игнорируется и число выводится в экспоненциальном формате с учетом Minwidth. Если значение Decpiaces больше 18, принимается значение 18. Следует учесть, что при указании подпараметра Decpiaces вещественное число всегда будет выводиться в формате с фиксированной точкой и требуемым количеством знаков в дробной части, даже если значение подпараметра Minwidth окажется недостаточным для размещения целой части: в этом случае значение Minwidth автоматически увеличивается.
Процедура writebn полностью идентична процедуре Write за исключением того, что выводимая последовательность символов автоматически завершается маркером eoln (свое название процедура получила от Write Line - писать строку). При вызове WriteLn можно опускать параметры Vi-в этом случае в файл передается пустая строка.
11.4. ТИПИЗИРОВАННЫЕ ФАЙЛЫ
Длина любого компонента типизированного файла строго постоянна, что дает возможность организовать прямой доступ к каждому из них (т. е. доступ к компоненту по его порядковому номеру).
Перед первым обращением к процедурам ввода-вывода указатель файла стоит в его начале и указывает на первый компонент с номером 0. После каждого чтения или записи указатель сдвигается к следующему компоненту файла. Переменные в списках ввода-вывода должны иметь тот же тип, что и компоненты файла. Если этих переменных в списке несколько, указатель будет смещаться после каждой операции обмена данными между переменными и дисковым файлом.
Таблица 11.3. Подпрограммы для работы с типизированными файлами
Function FilePos (var F): Longint; |
Возвращает текущую позицию в файле, т. е. номер компонента, который будет обрабатываться следующей операцией ввода-вывода |
Function FileSize (var F): Longint;
|
Возвращает количество компонентов файла. Чтобы переместить указатель в конец типизированного файла, можно написать: seek (FileVar, FileSize(FileVar)); |
Procedure Seek(var F; N: Longint) ; |
Смещает указатель файла F к требуемому компоненту: n - номер компонента файла (первый компонент файла имеет номер 0) |
Procedure Read(var F, VI,..., Vn) ; |
Читает данные из типизированного файла f: v< - переменные такого же типа, что и компоненты файла |
Procedure Write(var F,P1, ...,Pn) |
Записывает данные в типизированный файл р: Pi - выражения такого же типа, что и компоненты файла |
11.5. НЕТИПИЗИРОВАННЫЕ ФАЙЛЫ
Нетипизированные файлы объявляются как файловые переменные типа File и отличаются тем, что для них не указан тип компонентов. Отсутствие типа делает эти файлы, с одной стороны, совместимыми с любыми другими файлами, а с другой - позволяет организовать высокоскоростной обмен данными между диском и памятью.
При инициации нетипизированного файла процедурами Reset или Rewrite можно указать длину записи нетипизированного файла в байтах. Например, так:
var
F: File;
begin
AssignFile(F,'myfile.dat');
Reset(f,512);
end.
Длина записи нетипизированного файла указывается вторым параметром при обращении к процедурам Reset или Rewrite, в качестве которого может использоваться выражение типа Longint. Если длина записи не указана, она принимается равной 128 байтам.
Object Pascal не накладывает каких-либо ограничений на длину записи нетипизированного файла за исключением требования положительности и ограничения максимальной длины 2 Гбайт (для Delphi 1 длина записи ограничивается 65535). Для обеспечения максимальной скорости обмена данными рекомендуется задавать длину, которая была бы кратна длине физического сектора дискового носителя информации (512 байт). Однако операции обмена данными с дисковыми устройствами в среде Windows кэшируются, т. е. осуществляются через промежуточный буфер памяти, поэтому обычно задают Recsize = 1, что позволяет обмениваться с файлом блоками любой длины начиная с одного байта.
При работе с нетипизированными файлами могут применяться все процедуры и функции, доступные типизированным файлам, за исключением Read и write, которые заменяются соответственно высокоскоростными Процедурами BlockRead И BlockWrite:
Procedure BlockRead(var F: File; var Buf; Count: Integer [;
var AmtTransferred: Integer]) ;
Procedure BlockWrite(var F: File; var Buf; Count: Integer [;
var AmtTransferred: Integer]);
Здесь Buf - буфер: имя переменной, которая будет участвовать в обмене данными с дисками; count - количество записей, которые должны быть прочитаны или записаны за одно обращение к диску;
AmtTransferred - необязательный параметр, содержащий при выходе из процедуры количество фактически обработанных записей.
За одно обращение к процедурам может быть передано до count*RecSize байт, где RecSize - длина записи нетипизированного файла. Передача идет начиная с первого байта переменной Buf. Программист должен позаботиться о том, чтобы длина внутреннего представления переменной Buf была достаточной для размещения всех count*Rec3ize байт при чтении информации с диска. Если при чтении указана переменная недостаточной длины или если в процессе записи на диск не окажется нужного свободного пространства, возникнет ошибка ввода-вывода, которую можно заблокировать, указав необязательный параметр AmtTransferred.
После завершения процедуры указатель смещается на count записей. Процедурами Seek, FilePos И FileSize можно обеспечить доступ к любой записи нетипизированного файла.
11.6. СРЕДСТВА WINDOWS ДЛЯ РАБОТЫ С ФАЙЛАМИ
Операционная система Windows имеет собственные средства работы с файлами, которые становятся доступны программе Delphi после ссылки на модуль Windows. Поскольку файловые средства Object Pascal реализуют подавляющее большинство программных запросов, в табл. 11.4 приводится лишь краткая информация о назначении соответствующих API-функций. За подробной информацией обращайтесь к справочной службе в файлах WIN32. hlp или WIN32SDK.HLp (для версии б
Эти файлы расположены В каталоге Programs Files | Common Files |
Borland Share | MSHelp).
Таблица 11.4. Средства Windows для работы с файлами
AreFileApisANSI |
Определяет, будут ли файловые операции использовать кодовую страницу ansi |
CopyFile |
Копирует содержимое одного файла в другой |
CreateDirectory |
Создает новый каталог на диске Создает новый каталог на диске |
CreateDirectoryEx |
Создает новый или открывает существующий файл |
CreateFile |
Связывает асинхронный ввод/вывод с файлом, что дает возможность получить извещение о завершении асинхронной операции |
CreateIoCompletionPort |
Определяет, переопределяет или уничтожает определение логического устройства ms-dos |
DefineDosDevice DeleteFile |
Уничтожает файл (в табл. 8.1 указан более удобный интерфейс вызова этой API-функции) операций |
FileIOCompletionROutine |
Связывает асинхронный ввод/вывод с подпрограммой для слежения за окончанием асинхронных |
FindClose
|
Освобождает память, выделенную функциям Find FirstFile - FindNextFile |
FindCloseChangeNotification
|
Освобождает память, выделенную функциям Find-FirstChangeNotification FindNextChangeNotification |
FindFirstChangeNotification |
Требует от Windows известить программу об изменении состояния каталога |
FindFirstFile |
Ищет первый файл из группы файлов |
FindNextChangeNotifi- cation |
Требует от Windows известить программу об очередном изменении состояния каталога |
FindNextFile |
Ищет следующий файл |
FlushFileBuffers |
Очищает файловый буфер |
GetBinaryType |
Определяет, является ли файл исполняемым и, если является, возвращает его тип |
GetCurrentDirectory |
Возвращает умалчиваемый каталог |
GetDiskFreeSpace |
Возвращает свободное дисковое пространство в байтах |
GetDriveType |
Возвращает тип диска (сменный, удаленный и т. п.) |
GetFileAttributes |
Возвращает атрибуты файла |
GetFileInformationBy Handle |
Возвращает системную информацию о файле |
GetFileSize |
Возвращает размер файла в байтах |
GetFileType |
Возвращает тип файла |
GetFullPathName
|
По короткому имени файла в формате ms-dos и windows 16 возвращает его полное имя в формате windows 32 |
GetLogicalDrives
|
Возвращает битовую 32-разрядную маску, определяющую текущий диск |
GetLogicalDriveStrings
|
Возвращает список всех дисков, зарегистрированных в Windows |
GetQueuedCompletion Status |
Требует от Windows выполнения асинхронного ввода/вывода и приостанавливает работу программы до завершения операций |
GetShortPathName |
Возвращает короткое имя файла в формате MS-DOS^Windows 16) |
GetSystemDirectory
|
Возвращает имя системного каталога Windows для размещения библиотек, драйверов, шрифтов и т. п. |
GetTempFileName
|
Возвращает уникальное имя файла для временного хранения данных |
GetTempPath
|
Возвращает маршрут поиска каталога, предназначенного для хранения временно используемых файлов |
Ge tVolumeIn fo rmat ion
|
Возвращает информацию о файловой подсистеме в целом и об указанном каталоге |
GetWindowsDirectory
|
Возвращает полное имя каталога Windows для размещения прикладных программ, файлов инициализации, файлов помощи и т. п. |
LockFile |
Защищает файл от доступа к нему из других программ |
LockFileEx |
Устанавливает способ использования файла другими программами |
MoveFile |
Переименовывает файл или каталог (с подкаталогами) |
MoveFileEx |
Переименовывает файл |
OpenFile |
Открывает существующий файл |
QueryDosDevice |
Получает информацию об именах дисков (устройств), используемых в MS-DOS |
ReadFile |
Читает данные из файла |
ReadFileEx |
Реализует асинхронное чтение данных из файла |
RemoveDirectory |
Удаляет пустой каталог |
SearchPath |
Ищет файл в заданном каталоге (каталогах) |
SetCurrentDirectory |
Устанавливает умалчиваемый каталог |
SetEndOfFile |
Перемещает файловый указатель в конец файла |
SetFileApisToANSI
|
Предписывает Windows использовать кодовую страницу ANSI при файловых операциях |
SetFileApisToOEM
|
Предписывает Windows использовать кодовую страни . MS-DOS при файловых операциях |
SetFileAttributes |
Устанавливает атрибуты файла |
SetFilePointer |
Перемещает файловый указатель на нужную позицию |
SetHandleCount |
Устанавливает количество файлов, одновременно используемых программой |
SetVolumeLabel |
Устанавливает новую метку тома (носителя информации) |
UnlockFile |
Снимает с файла защиту, установленную функцией LockFile |
UnlockFileEx |
Снимает с файла защиту, установленную функцией LockFileEx |
WriteFile |
Записывает данные в файл |
WriteFileEx |
Реализует асинхронную запись в файл |
11.7. ОТОБРАЖЕНИЕ ФАЙЛОВ В ПАМЯТЬ
Для работы с файлом динамической подкачки страниц виртуальной памяти в Windows 32 используется механизм отображения файлов в адресное пространство программы. Соответствующие функции API доступны любой программе и могут применяться к любому файлу (кстати, таким способом загружается в адресное пространство процесса исполняемый файл). В результате отображения программа может работать с файловыми данными как с данными, размещенными в динамической памяти. Такая возможность не только в большинстве случаев повышает скорость работы с данными, но и предоставляет программисту уникальные средства обработки сразу всех записей файла. Например, он может единственным оператором проверить входит ли заданный образец поиска в любую строку текстового файла. Отображение файла осуществляется в три приема.
11.7.1. Создание/открытие файла
Вначале файл создается обращением к функции
function FileCreate(FileName: String): Integer;
или открывается с помощью
function FileOpen(const FileName:
String; Mode: LongWord): Integer;
В обеих функциях FileName - имя файла, возможно, с маршрутом доступа. Параметр Mode определяет режим доступа к файлу и может принимать одно из следующих значений: fmOpenRead - только чтение; fmOpenWrite - только Запись; fmOpenReadWrite - чтение и запись. с помощью операции or эти константы можно комбинировать с одной из следующих регулирующих совместный доступ к файлу нескольких Программ: fmShareExclusive - совместный доступ Запрещен; fmShareDenyWrite - Другим Программам запрещается запись; fmShareDenyRead - другим программам запрещается чтение; fmSchareDenyNone - совместный доступ неограничен. Обе функции возвращают дескриптор созданного (открытого) файла или 0, если операция оказалась неуспешной.
11.7.2. Создание объекта отображения
На втором этапе создается объект отображения в память. Для этого используется такая функция:
function CreateFileMapping(hFile: THandle; IpFileMapping-Attributes: PSecurityAttributes; flProtect, dwMaximumSize-High, dwMaximumSizeLow: DWord; IpName: PChar): THandle;
Здесь hFile - дескриптор файла; ipFileMappingAttributes - указа-тель на структуру, в которой определяется, может ли создаваемый объект порождать дочерние объекты (обычно не может - nil); flProtect -определяет тип защиты, применяемый к окну отображения файла (см.ниже); dwMaximumSizeHigh, dwMaximumSizeLow - соответственно Старшие и младшие 32 разряда размера файла; если вы будете отображать файлы длинной до 4 ГбаЙТ, поместите В dwMaximumSizeHigh 0, а В dwMaximumSizeLow - длину файла; если оба параметра равны 0, размер окна отображения равен размеру файла; ipName - имя объекта отображения или nil.
Параметр flProtect задает тип защиты, применяемый к окну просмотра файла, и может иметь одно из следующих значений: page_readonly - файл можно только читать (файл должен быть создан или открыт В режиме fmOpenRead); PAGE_READWRITE - файл можно читать и записывать в него новые данные (файл открывается в режиме fmOpenReadWrite); PAGE_WRITECOPY - файл открыт для записи и чтения, однако обновленные данные сохраняются в отдельной защищенной области памяти (отображенные файлы могут разделяться программами, в этом режиме каждая программа сохраняет изменения в отдельной области памяти или участке файла подкачки); файл открывается В режиме fmOpenReadWrite или fmOpenWrite; этот тип защиты нельзя использовать в Windows 95/98. С помощью операции or к параметру fiprotect можно присоединить такие атрибуты:
sec_commit - выделяет для отображения физическую память или участок файла подкачки; sec_image - информация об атрибутах отображения берется из образа файла; sec_nocashe - отображаемые данные не кэшируются и записываются непосредственно на диск;
sec_reserve - резервируются страницы раздела без выделения физической памяти. Функция возвращает дескриптор объекта отображения или 0, если обращение было неудачным.
11.7.3. Создание окна просмотра
Наконец, на третьем этапе создается окно просмотра, т. е. собственно отображение данных в адресное пространство программы:
function MapViewOfFile(hFileMappingObject: THandle; dwDesiresAccess: DWord; dwFileOffsetHigh, dwFileIffsetLow, dwNumberOfBytesToMap: DWord): Pointer;
Здесь hFileMappingObject -дескриптор объекта отображения; dwDesiresAccess - определяет способ доступа к данным и может иметь одно из следующих значений: file_map_write - разрешает чтение и запись, При ЭТОМ В функции CreateFileMapping должен использоваться атрибут page_readwrite; file_map_read - разрешает только чтение, в функции CreateFileMapping должен использоваться атрибут
PAGE_READONLY или PAGE_READWRITE; FILE_MAP_ALL_ACCESS - тоь же, что и
file_map_write; file_map_copy - данные доступны для записи и чтения, однако обновленные данные сохраняются в отдельной защищенной области памяти; в функции CreateFileMapping должен использоваться атрибут page_writecopy; dwFileOffsetHigh, dwFileIffsetLow -определяют соответственно старшие и младшие разряды смещения от начала файла, начиная с которого осуществляется отображение;
dwNumberOfBytesToMap - определяет длину окна отображения (0 - длина равна длине файла). Функция возвращает указатель на первый байт отображенных данных или nil, если обращение к функции оказалось неуспешным.
11.7.4. Освобождение ресурсов отображения
После использования отображенных данных ресурсы окна отображения нужно освободить функцией function UnMapViewOfFile(IpBaseAddress: Pointer): BOOL; единственный параметр обращения к которой должен содержать адрес первого отображенного байта, т. е. адрес, возвращаемый функцией MapViewOfFile. Закрытие объекта отображения и самого файла осуществляется обращением к функции
function CloseHandle(hObject: THandle).
11.7.5. Пример использования
В следующем листинге приводится текст модуля, который создает окно, показанное на рис. 11.1.
Программа создает дисковый файл, состоящий из 100000 случайных вещественных чисел (длину файла можно выбрать другой, если изменить значение редактора длина массива). Файл с именем test.dat создается путем отображения файла в память (кнопка память) и традиционным способом (кнопка Файл). В обоих случаях показывается время счета (процессор 400 МГц, память 64 Мбайт). Чем больше частота и память, тем больше будет разница во времени.
Рис. 11.1. Окно демонстрационнойпрограммы
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, Spin;
type
TFormI = class(TForm)
btMem: TButton;
btFile: TButton;
se: TSpinEdit;
Labell: TLabel;
pb: TProgressBar;
Label2: TLabel;
IbMem: TLabel;
IbFile: TLabel;
procedure btMemClick(Sender: TObject);
procedure btFileClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.btMemClick(Sender: TObject);
// Создание файла методом его отображения
type
PReal = ^Real;
var
HFile, HMap: THandle;
AdrBase, AdrReal: PReal;
k: Integer;
FSize: Cardinal;
BegTime: TDateTime;
begin
BegTime := Time; // Засекаем время пуска
// Готовим ProgressBar:
pb.Max := se.Value;
pb.Position := 0;
pb.Show;
FSize := se.Value * SizeOf(Real); // Длина файла HFile := FileCreate('test.dat'); // Создаем файл
if HFile = 0 then // Ошибка: возбуждаем исключение
raise Exception.Create('Ошибка создания файла');
try
// Отображаем файл в память HMap := CreateFileMapping(
HFile, NIL, PAGE_READWRITE, 0, FSize, NIL);
if HMap = 0 then // Ошибка: возбуждаем исключение
raise Exception.Create('Ошибка отображения файла');
try
// Создаем окно просмотра:
AdrBase := MapViewOfFile(
HMap, FILE_MAP_WRITE, О, О, FSize) ;
if AdrBase = NIL then // Ошибка: возбуждаем исключение
raise Exception.Create('Невозможно просмотреть файл');
// Сохраняем начальный адрес для правильной ликвидации
// окна просмотра:
AdrReal := AdrBase;
for k := 1 to se.Value do
begin
AdrReal^ := Random; // Помещаем в файл новое число
// Перед наращиванием текущего адреса необходимо
// привести его к типу Integer или Cardinal:
AdrReal := Pointer(Integer(AdrReal) + SizeOf(Real));
IbMem.Caption := IntToStr(k);
pb.Position := k;
Application.ProcessMessages;
end;
// Освобождаем окно просмотра:
UnmapViewOfFile(AdrBase) finally
// Освобождаем отображение CloseHandle(HMap)
end
finally
// Закрываем файл CloseHandle(HFile)
end;
// Сообщаем время счета pb.Hide;
IbMem.Caption := TimeToStr(Time-BegTime)
end;
procedure TFormI.btFileClick(Sender: TObject) ;
// Создание файла обычным методом var
F: File of Real;
k: Integer;
BegTime: TDateTime;
R: Real; // Буферная переменная для обращение к Write
begin
BegTime := Time; // Засекаем начальное время счета // Готовим ProgressBar:
pb.Max := se.Value;
pb.Position := 0;
pb.Show;
// Создаем файл:
AssignFile(F, 'test.dat');
Rewrite(F);
for k := 1 to se.Value do
begin
R := Random; // Параметрами обращения к Write
Write(F, R); // могут быть только переменные
IbFile.Caption := IntToStr(k);
pb.Position := k;
Application.ProcessMessages;
end;
CloseFile(F) ;
pb.Hide;
IbFile.Caption :=TimeToStr(Time-BegTime)
end;
end.
11.8. ОБЪЕКТНАЯ МОДЕЛЬ РАБОТЫ С ФАЙЛАМИ
В Delphi используется абстрактный класс TStream (поток данных), который является основой для работы с файлами как с объектами. В случае объектов совершенно не важно, что именно является носителем информации - дисковый ли файл, ленточный носитель или оперативная память. В специализированных потомках TStream определены стандартные методы Read, write и seek, открывающие полный доступ к файловому объекту. Сам объект создается конструктором и уничтожается деструктором.
Поток не, учитывает специфику хранящихся в файле данных, т. е. по существу работает с данными, как если бы хранились в не типизированном файле.
В следующем примере файл с именем, хранящимся в Editl.Text,
копируется В файл Edit2 . Text.
procedure TFormI.CopyFileClick(Sender:TObject) ;
var
Streami, Stream2: TStream;
begin
Streami:=TFileStream.Create(Editi.Text,
fmOpenRead or fmShareDenyWrite) ;
try
Stream2 :=TFileStream.Create(Edit2.Text,
fmOpenCreate or fmShareDenyRead);
try
Stream2.CopyFrom(Streami, Streami.Size);
finally
Stream2.Free;
finally
Stream1.Free
end;
Абстрактный класс т Stream лишь декларирует ключевые методы Read и write, которые перекрываются в его наследниках для специализации операций:
Любой наследник получает в свое распоряжение метод copyFrom, с помощью которого он может прочитать содержимое другого потока. Таким способом можно, например, файловые данные расположить в памяти или наоборот - содержимое памяти записать в дисковый файл.
Точно так же от TStream наследуются множество других полезных методов, в том числе:
Следующий обработчик bbRunciick выведет в редактор mmOutput свойства кнопки bbRun:
procedure TfmExample.bbRunClick(Sender: TObject);
var
MemSourceStream, MemDestStream: TMemoryStream;
begin
MemSourceStream := TMemoryStream.Create;
try
MemDestStream := TMemoryStream.Create;
try
MemSourceStream.WriteComponent(bbRun);
MemSourceStream.Seek(0, soFromBeginning) ;
ObjectBinaryToText(MemSourceStream, MemDestStream);
MemDestStream.Seek(0, soFromBeginning) ;
mmOutput.Lines.LoadFromStream(MemDestStream) finally
MemDestStream.Free
end;
finally
MemSourceStream.Free
end;
end;
Комментарий к программе
Вначале свойства компонента записываются в поток MеmSourceStream. Чтобы полученным таким образом двоичным данным придать “читабельный” вид, используется процедура ObjectBinaryToText, которая преобразует данные из MemSourceStream и помещает иx в MemDestStream. Содержимое этого второго потока и выводится
В окне mmOutput. Вид окна работающей программы представлен на рис. 11.2.
Рис. 11.2. Свойства компонента bbRun