Delphi 3. Библиотека программиста

Файловые операции чтения/записи


Разобравшись с анализом командных строк, мы приступаем к следующей крупной подзадаче- файловому вводу/выводу. Разумеется, при простейших посимвольных (или построчных) преобразованиях текстовых файлов можно пользоваться функциями Read и Write (или ReadLn и WriteLn) в сочетании с Eof и Eoln. Например, процедура DoFilter из листинга 1.7 копирует символы из входного файла в выходной, преобразуя их к верхнему регистру.

Листинг 1.7. Перевод символов в верхний регистр

procedure DoFilter; const nOptions = 2; Options : Array [1..nOptions] of OptionRec = ( (OptionChar : "i"; Option : otFilename; Filename : ""), (OptionChar : "o"; Option : otFilename; Filename : "") ); var cRslt : Boolean; iRec : pOptionRec; oRec : pOptionRec; InputFile : Text; OutputFile : Text; c : char; begin cRslt := CmdLine.ProcessCommandLine (@Options, nOptions); if (not cRslt) then Halt; { Убедимся в том, что были заданы имена входного и выходного файлов } iRec := CmdLine.GetOptionRec (@Options, nOptions, "i"); if (iRec^.Filename = "") then begin WriteLn ("Error: input file expected"); Halt; end; oRec := CmdLine.GetOptionRec (@Options, nOptions, "o"); if (oRec^.Filename = "") then begin WriteLn ("Error: output file expected"); Halt; end; { Открываем входной файл - без проверки ошибок} Assign (InputFile, iRec^.Filename); Reset (InputFile); { Создаем выходной файл - без проверки ошибок} Assign (OutputFile, oRec^.Filename); Rewrite (OutputFile); { Читаем и преобразуем каждый символ } while (not Eof (InputFile)) do begin Read (InputFile, c); c := UpCase (c); Write (OutputFile, c); end; Close (InputFile); Close (OutputFile); end;

У данной версии программы FILTER есть два недостатка. Во-первых, она еле ползает - словно змея, пробуждающаяся от зимней спячки. Если у вас найдется мегабайтовый текстовый файл и несколько свободных минут, убедитесь сами. Во-вторых, она работает только с текстовыми файлами. Для одноразового приложения сойдет и так, но мы пишем шаблон для различных

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

Класс TFilterFile из листинга 1.8 предназначен для организации быстрых побайтовых операций с файлами в программах-фильтрах. Он инкапсулирует все детали буферизации и по возможности избавляет программиста от необходимости помнить о многочисленных житейских проблемах работы с файлами (вам остается лишь вызвать Open и Close).

Листинг 1.8. Реализация класса TFilterFile из файла FILEIO.PAS

{ FILEIO.PAS - Файловый ввод/вывод для программ-фильтров Автор: Джим Мишель Дата последней редакции: 04/05/97 } {$I+} { Использовать исключения для обработки ошибок } unit fileio; interface type FileIOMode = (fioNotOpen, fioRead, fioWrite); BuffArray = array[0..1] of byte; pBuffArray = ^BuffArray; TFilterFile = class (TObject) private FFilename : String; F : File; FBufferSize : Integer; FBuffer : pBuffArray; FBytesInBuff : Integer; FBuffIndx : Integer; FFileMode : FileIOMode; function ReadBuffer : boolean; function WriteBuffer : boolean; public constructor Create (AName : String; ABufSize : Integer); destructor Destroy; override; function Open (AMode : FileIOMode) : Boolean; procedure Close; function Eof : Boolean; function GetByte : byte; function PutByte (b : byte) : boolean; end; implementation { TFilterFile } { Create - подготавливает, но не открывает файл } constructor TFilterFile.Create ( AName : String; ABufSize : Integer ); begin inherited Create; FFilename := AName; FBufferSize := ABufSize; FBytesInBuff := 0; FBuffIndx := 0; FFileMode := fioNotOpen; { Назначаем, но не открываем } Assign (F, FFilename); { Выделяем память для буфера } GetMem (FBuffer, FBufferSize); end; { Destroy - закрывает файл (если он открыт) и уничтожает объект } destructor TFilterFile.Destroy; begin { Если файл открыт, закрываем его } if (FFileMode <> fioNotOpen) then begin Self.Close; end; { Если был выделен буфер, освобождаем его } if (FBuffer <> Nil) then begin FreeMem (FBuffer, FBufferSize); FBuffer := Nil; end; inherited Destroy; end; { Open - открыть файл в нужном режиме } function TFilterFile.Open ( AMode : FileIOMode ) : Boolean; var SaveFileMode : Byte; begin Result := True; SaveFileMode := FileMode; { переменная FileMode определена в модуле System } { Пытаемся открыть файл } try case AMode of fioRead : begin FileMode := 0; Reset (F, 1); end; fioWrite : begin FileMode := 1; Rewrite (F, 1); end; end; FFileMode := AMode; except Result := False; end; FBytesInBuff := 0; FBuffIndx := 0; FileMode := SaveFileMode; end; { Close - закрывает файл, при необходимости сбрасывая буфер } procedure TFilterFile.Close; begin { Если буфер записи не пуст, записываем его } if ((FFileMode = fioWrite) and (FBytesInBuff > 0)) then begin WriteBuffer; end; try { Закрываем файл } System.Close (F); finally FFileMode := fioNotOpen; end; end; { ReadBuffer - читает блок из файла в буфер } function TFilterFile.ReadBuffer : Boolean; begin Result := True; if (Self.Eof) then begin Result := False; end else begin try BlockRead (F, FBuffer^, FBufferSize, FBytesInBuff); except Result := False; end; end; end; { GetByte - возвращает следующий байт из файла. При необходимости читает из файла в буфер } function TFilterFile.GetByte : byte; begin if (FBuffIndx >= FBytesInBuff) then begin if (not ReadBuffer) then begin Result := 0; Exit; end else begin FBuffIndx := 0; end; end; Result := FBuffer^[FBuffIndx]; Inc (FBuffIndx); end; { WriteBuffer - записывает блок из буфера в файл } function TFilterFile.WriteBuffer : Boolean; begin Result := True; try BlockWrite (F, FBuffer^, FBytesInBuff); except Result := False; end; if (Result = True) then begin FBytesInBuff := 0; end; end; { PutByte - заносит байт в буфер. При необходимости записывает буфер в файл } function TFilterFile.PutByte (b : byte) : Boolean; begin if (FBytesInBuff = FBufferSize) then begin if (not WriteBuffer) then begin Result := False; Exit; end else begin FBytesInBuff := 0; end; end; FBuffer^[FBytesInBuff] := b; Inc (FBytesInBuff); Result := True; end; { Eof - возвращает True, если был достигнут конец входного файла } function TFilterFile.Eof : Boolean; begin Result := (FBuffIndx >= FBytesInBuff); if Result then begin try Result := System.Eof (F); except Result := True; end; end; end; end.

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

Листинг 1.9. Использование класса TFilterFile вместо
стандартного файлового ввода/вывода

{ FILTMAIN.PAS - основной рабочий модуль программы Filter. Автор: Джим Мишель Дата последней редакции: 04/05/97 } unit filtmain; interface { DoFilter выполняет всю работу } procedure DoFilter; implementation uses CmdLine, FileIO; procedure DoFilter; const nOptions = 2; Options : Array [1..nOptions] of OptionRec = ( (OptionChar : "i"; Option : otFilename; Filename : ""), (OptionChar : "o"; Option : otFilename; Filename : "") ); BigBufferSize = 65536; var cRslt : Boolean; iRec : pOptionRec; oRec : pOptionRec; InputFile : TFilterFile; OutputFile : TFilterFile; c : char; begin cRslt := CmdLine.ProcessCommandLine(@Options, nOptions); if (not cRslt) then Halt; { Убедимся в том, что были заданы имена входного и выходного файлов } iRec := CmdLine.GetOptionRec (@Options, nOptions, "i"); if (iRec^.Filename = "") then begin WriteLn ("Error: input file expected"); Halt; end; oRec := CmdLine.GetOptionRec (@Options, nOptions, "o"); if (oRec^.Filename = "") then begin WriteLn ("Error: output file expected"); Halt; end; { Создаем и открываем входной файл } InputFile := TFilterFile.Create (iRec.Filename, BigBufferSize); if (not InputFile.Open (fioRead)) then begin WriteLn ("Error opening input file"); Halt; end; { Создаем и открываем выходной файл } OutputFile := TFilterFile.Create (oRec.Filename, BigBufferSize); if (not OutputFile.Open (fioWrite)) then begin WriteLn ("Error opening output file"); Halt; end; { Обрабатываем каждый символ } while (not InputFile.Eof) do begin c := char (InputFile.GetByte); c := UpCase (c); if (not OutputFile.PutByte (byte (c))) then begin WriteLn ("Write error"); Halt; end; end; InputFile.Close; InputFile.Free; OutputFile.Close; OutputFile.Free; end; end.

Содержание раздела