12.8. Бестиповые файлы и операции ввода-вывода
Стандарт Турбо Паскаля вводит особый файловый тип, являющийся, по сути, обобщенным файловым типом. Мы будем называть его в дальнейшем бестиповым файлом, поскольку его обозначение состоит только из слова File без указания типа компонент.
Бестиповый файл — это очень мощное средство работы с файлами, так как он позволяет манипулировать с данными, не задумываясь об их типе. С его помощью можно записывать на диск произвольные участки рабочей памяти ПЭВМ и считывать их в память диска. Можно также преобразовывать считываемые из бестипового файла данные в любой формат посредством приведения типов. В этом разделе будут рассмотрены некоторые примеры использования бестиповых файлов.
- 250 -
Ввод-вывод в бестиповые файлы осуществляется специальными процедурами BlockRead и BlockWrite. Кроме того, расширяется синтаксис процедур Reset и Rewrite. В остальном принципы работы остаются такими же, как и с типизированными файлами. Перед использованием файловая переменная f (логический файл)
VAR
f : File;
должна быть связана с конкретным физическим файлом через вызов оператора Assign. Далее файл должен быть открыт для чтения или записи с помощью вызова процедуры Reset (f) или Rewrite (f) соответственно. После окончания работы файл должен быть закрыт процедурой Close (f).
Открывая бсстиповый файл для работы, мы неявно устанавливаем размер буфера передачи данных равным 128 байт. Однако можно явным способом указать иной размер буфера (чем он больше, тем быстрее происходит ввод-вывод), исходя из ресурсов памяти и удобства работы с данными. Для задания буфера надо после оператора Assign открывать файл расширенной записью процедур:
Reset( VAR f : File; BufSize : Word )
и
Rewrite( VAR f : File; BufSize : Word ).
Параметр BufSize задает число байтов, считываемых из файла за одно обращение к нему или записываемых в него. Чем больше значение BufSize, тем быстрее происходит обмен данными между носителем файла (как правило, диском) и оперативной памятью ПЭВМ. Но тем больше и расход памяти. Ведь именно в ней располагается буфер файла.
Минимальный блок, который может быть записан или прочитан из файла, это 1 байт. Чтобы задать его, надо установить именно такую величину буфера при открытии файла. Максимальный размер блока не может превышать 64K.
Во время отладки программ в среде Турбо Паскаль можно проверить размер буфера, поместив в окно просмотра (Watch) или анализа (Evaluate) файловую переменную f, приведенную к типу FileRec (для этого может понадобиться подключение модуля DOS):
{ FileRec(f), R
или, конкретнее,
FileRec( f ).BufSize
Для чтения или записи данных в бестиповый файл стандартные процедуры Read и Write не годятся. Их заменяют здесь процедуры:
BlockRead(VAR f : File; VAR Destin; Count : Word [; VAR ReadIn : Word])
и
BlockWrite(VAR f : File; VAR Source; Count : Word [; VAR WriteOut : Word]).
Эти процедуры осуществляют чтение в переменную Destin и запись из переменной Source не компонентов файла или его строк, а блоков, состоящих из того количества байтов, которое определено для буфера файла f. Если Count больше 1, то за одно обращение будет считано Count емкостей буфера. Значение Count, меньшее единицы, не имеет смысла. Всегда должно выполняться условие:
Count*Размер_буфера < 64K.
Необязательный параметр ReadIn возвращает число блоков (буферов), считанное текущей операцией BlockRead. Аналогичный параметр WriteOut процедуры BlockWrite после каждой операции записи показывает число блоков (буферов), записанное в данный файл этой операцией.
Если операции записи или чтения прошли успешно, то значения ReadIn и WriteOut будут равны соответствующим значениям параметров Count. Но если произошел сбой при вводе-выводе, и заказанное число блоков не перенеслось, то параметры ReadIn и WriteOut будут содержать целое число удачно перенесенных блоков (неудача посередине блока практически равносильна отмене его чтения или записи).
Таким образом, эти параметры могут использоваться для контроля выполнения операций BlockRead и BlockWrite:
VAR
Fr,Fw : File; { файловые переменные }
ReadIn, WriteOut : Word; { переменные контроля }
Destin : ....... ; { приемник при чтении }
Source : ………; { источник при записи }
- 252 -
BEGIN
...
BlockRead( Fr, Destin, 3, ReadIn );
if ReadIn <> 3 then обработка ошибки чтения ;
BlockWrite( Fw, Source, 4, WriteOut );
if WriteOut <> 4 then обработка ошибки записи ;
...
END.
Если в вызове BlockRead последний параметр не указан, то невозможность считать заданное число блоков вызовет ошибку ввода-вывода и остановку программы.
Процедуры BlockRead и BlockWrite не имеют списков ввода и вывода, поскольку не определен тип компонента файла. Взамен их в вызовах присутствуют бестиповые переменные. Адрес начала переменной в памяти соответствует адресу области памяти, начиная с которого заданное количество байт будет выведено в файл при записи или помещено в память из файла при чтении. Передавая переменную процедуре, мы всегда тем самым передаем адрес ее содержимого, точнее, первого байта ее значения. Если переменная X — массив
VAR
X : Array [1..10] of ... ;
то вызов BlockWrite или BlockRead с ней будет принимать за точку начала отсчета блока первый элемент массива. Можно более явно указать в вызове начало блока как X [1]. Но если подставить X[5], то отсчет блока будет вестись уже сразу с пятого элемента массива. Особенно осторожно надо будет обращаться со ссылками при подстановке их в BlockWrite и BlockRead. Ссылки должны быть разыменованы, с тем чтобы показывать на данные, а не на место в памяти, где хранится сама ссылка. Так, если определена ссылочная переменная P:
TYPE
Dim = Array [0..999] of Real; { массив }
VAR
P : ^Dim; { ссылка на массив }
f : File; { бестиповый файл }
то после создания динамического массива P^ вызовом процедуры New(Р) и его заполнения, он может быть записан в файл f следующим образом:
- 253 -
Assign( f, 'DIMFILE.DAT' ); { связывание f с диском }
Rewrite( f, SizeOf( Dim ) ); { открытие f для записи }
BlockWrite( f, Р^, 1 ); { запись массива в файл }
{ Ссылка P разыменована! }
Close( f ); { закрытие файла f }
Если ошибочно написать P вместо P^, то процедура сработает, но сохранит в файле кусок памяти, начиная с Addr(P), который вовсе не равен адресу динамического массива Addr(P^)!
Чтобы прочитать впоследствии записанный массив из файла, нужно «развернуть» направление вывода данных:
{ Место под массив P^ должно быть зарезервировано! }
New( Р );
Assign( f, 'DIMFILE.DAT' ); { связывание f с диском }
Reset( f, SizeOf( Dim ) ); { открытие f для чтения }
BlockRead( f, P^, 1 ); { чтение массива из файла}
{ Ссылка Р разыменована! }
Close( f ); { закрытие файла f }
Перед чтением блока в динамическую переменную (здесь: P^) она должна быть корректным образом создана (через вызов New либо GetMem или присвоением значения адреса), иначе последствия будут непредсказуемыми.
Блочный способ работы с файлами весьма эффективен по времени, и если программа использует крупные массивы предварительно вычисляемых констант, то может оказаться более выгодным вынести их вычисления в отдельную программу, которая затем сохранит их на диске, а в расчетной программе просто вставить операторы блочного чтения уже рассчитанных значений. В таких случаях можно даже сыграть на особенностях компилятора Турбо Паскаля. Обычно при компиляции программ память под статические массивы (но не под динамические) отводится в порядке их следования в описании. Так, если описаны
VAR
A, B, C : Array [1..2000] of Real;
то их элементы выстраиваются в одну сплошную цепочку. Иными словами:
Addr(B) = Addr(A) + SizeOf(A),
Addr(C) = Addr(B) + SizeOf(B).
(Это верно не только для массивов, но и для любых статических структур, кроме объектов: память в пределах блока описания переменных отводится последовательно по мере их следования.)
- 254 -
Используя этот факт, можно записать или считать блоком сразу несколько структур данных, приняв за начало блока первую из них:
Assign( f, 'ABC.DAT' );
Rewrite( f, SizeOf(A) ); { открыть f и записать в }
BlockWrite( f, A, 3); { него три блока сразу }
Close( f );
или
Reset( f, SizeOf(A) ); { открыть f и считать из }
BlockRead( f, A, 3); { него три блока сразу } Close( f );
Другой, более специфичной областью применения бестиповых файлов является работа с системными областями памяти ПЭВМ, в том числе с видеопамятью. Одной из иллюстраций этого является, например, запись текстовых и графических изображений, рассматриваемая в разд. 20.4 и 22.3.