Потоки в VCL

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


Базовым классом всех потоков в VCL Win32 является TStream. В нем объявлены основные свойства и методы, позволяющие организовать чтение и запись данных. Но сам класс TStream — абстрактный, и объекты этого класса создавать нельзя. Мы рассмотрим два его дочерних класса — класс TFileStream, созданный специально для работы с файлами, и класс TMemoryStream, который описывает поток в памяти.


Объект класса TFileStream связывается с файлом в момент вызова конструктора. Первым аргументом в него передается строка с именем файла, а вторым аргументом — флаги режима, в котором открывается файл. Например, следующий код создает файл My.dat и открывает его для записи:

var T: TFileStream;
...
T:= TFileStream.Create('My.dat', fmCreate);

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

Т:= TFileStream.Create('My.dat', fmOpenRead);


Флаг fmOpenWrite откроет файл для записи. В этом случае можно заменять или дополнять данные, содержащиеся в файле. Флаг fmOpenReadWrite дает доступ и для чтения, и для записи. В этом режиме можно читать, редактировать и дополнять данные, имеющиеся в файле.

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

Так что если вам надо записать в файл, например, массив действительных чисел, то в VCL Win32 это легко делается одним оператором.

Пусть, например, объявлен динамический массив действительных чисел:

var A: array of Double;


Он заполняется какими-то результатами расчета, после чего его надо занести в файл, с которым соединен поток Т. Поток для этого должен быть открыт в режиме fmCreate, или fmOpenWrite, или fmOpenReadWrite.

В приложении VCL Win32 запись производится одним оператором:

Т.Write(A, sizeof(Double) * Length(A));

В вызове метода Write второй аргумент задан равным числу байтов, содержащихся в одном элементе типа Double, умноженному на число элементов массива. В результате в файл запишется весь массив А.


В приведенных примерах запись производится, начиная с первой позиции файла. Если файл был пустой (открывался в режиме fmCreate), то файл будет содержать только сделанную запись. Но если в файле уже было нечто записано и он открывался в режиме fmOpenWrite или fmOpenReadWrite, то новая запись будет сделана поверх того, что уже было записано в файле. Если новая запись короче прежней, то после ее окончания в файле останется конечная часть предшествующих записей. В потоке имеется свойство Position — текущая позиция. Именно это свойство определяет позицию, в которую производится запись методом Write. В момент создания потока Position = 0. А после каждого вызова метода Write позиция сдвигается на число записанных байтов.

Свойство Position можно изменять перед записью, управляя таким образом позицией, в которую производится запись. Например, в файле, открытом в режиме fmOpenWrite или fmOpenReadWrite, можно новые данные записывать в конец файла, не уничтожая содержащуюся в файле информацию. Для этого перед операторами вызова Write надо вставить оператор:


Т.Position:= Т.Size;


Этот оператор задает текущую позицию равной Size — свойству потока, содержащему общее число байтов в файле.


Чтение данных из потока осуществляется методом Read. Поток при этом должен быть создан в режиме fmOpenRead или fmOpenReadWrite. Параметры метода Read не отличаются от рассмотренных ранее для метода Write. Так что чтение файла, в который в предыдущих примерах были записаны данные, в динамический массив А в приложении VCL Win32 производится следующим образом:


SetLength(A, T.Size div sizeof(Double));

T.Read(A, T.Size);


Для определения размера массива использовано свойство Size потока, указывающее число байтов в потоке, т.е. в файле. Частное от деления этого значения на число байтов в типе Double дает количество записанных в файле действительных чисел. А затем все эти числа читаются одним вызовом метода Read.


В приведенных примерах чтение производилось, начиная с первой позиции файла, поскольку, как уже говорилось, в момент создания потока его текущая позиция Position = 0. А после каждого вызова метода Read позиция сдвигается на число прочитанных байтов.


Задавая значение свойства Position, вы можете читать не весь файл, а только тот записанный в нем элемент, который вам в данный момент нужен. Таким образом, файл обеспечивает произвольных доступ к записанным данным. Если вы знаете, что в файле записаны только значения типа Double, и вам надо прочитать в некоторую переменную В элемент с порядковым номером I, то это можно сделать операторами:


Т.Position:= (I - 1) * sizeof (Double);

Т.Read(В, sizeof(Double));


Позицию можно изменять не только свойством Position, но и методом Seek. Первый параметр этого метода указывает, на сколько должна быть сдвинута текущая позиция. Положительные значения соответствуют сдвигу к концу, а отрицательные — к началу потока. Второй параметр метода указывает, от какой точки отсчета задается сдвиг первым параметром. Этот параметр может принимать значения soBeginning — сдвиг относительно начала потока, soCurrent — сдвиг относительно текущей позиции, soEnd — сдвиг относительно конца потока (тогда сам сдвиг должен задаваться отрицательным). Так что в предыдущем примере задание позиции можно было бы выполнить не прямым изменением значения Position, а оператором:


T.Seek((I - 1) * sizeof(Double), soBeginning);


Впрочем, если требуемая позиция отсчитывается от начала файла, то вряд ли имеет смысл использовать метод Seek. Проще задать значение Position. Но если, например, требуется прочитать элемент с порядковым номером I, отсчитываемым от конца файла, то, может быть, использование метода Seek проще:


T.Seek(-I * sizeof(Double), soEnd);


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


Т.Position:= (I - 1) * sizeof(Double);

T.Read(B, sizeof(Double));

T.Position:= T.Position - sizeof(Double);

T.Write(B * 10, sizeof(Double));

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


У потока имеется еще один интересный метод — CopyFrom. Он копирует в данный поток из другого потока, заданного первым параметром, число байтов, заданное вторым параметром. Если второй параметр задан равным 0, то копируется поток целиком. Копирование начинается с текущей позиции потока-источника в текущую позицию потока-приемника. Этот метод дает простой и эффективный способ копирования любых файлов. Например:


var T1, T2: TFileStream;

...

T1:= TFileStream.Create('f1.dat', fmOpenRead);

T2:= TFileStream.Create('f2.dat', fmCreate);

T2.CopyFrom(T1, 0);

FreeAndNil(T1);

FreeAndNil(T2);


Приведенный код копирует файл f 1. dat в файл f 2. dat. Заключительные операторы закрывают потоки Т1 и Т2.

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


var T1, T2: TFileStream;

...

T1:= TFileStream.Create('f1.dat', fmOpenRead);

Т2:= TFileStream.Create('f2.dat', fmOpenWrite);

Т2.Position:= T2.Size;

T2.CopyFrom(T1, 0);

FreeAndNil(T1);

FreeAndNil(T2);


В приведенном коде подчеркиванием выделены отличия от предыдущего примера. Файл f2.dat открывается в режиме fmOpenWrite, так что содержащиеся в нем данные сохраняются. Следующий оператор сдвигает текущую позицию потока Т2 в конец файла. В результате копия файла f1.dat записывается в конец файла f2.dat.


Теперь рассмотрим особенности использования потоков класса TMemoryStream. Поток этого класса создается в памяти. При своем создании он не связывается ни с каким источником. Если требуется связать поток с файлом, то используется метод потока LoadFromFile, в который передается имя файла. Метод загружает в поток содержимое файла, уничтожая данные, которые находились в потоке до этого. Размер отводимой памяти и соответственно значение свойства Size в точности становится равным размеру файла. После этого можно применять к потоку любые методы записи и чтения, которые были рассмотрены выше для потока TFileStream. Поскольку все операции производятся в памяти, эффективность их высокая. Если данные в потоке изменяются, то сохранить их в дальнейшем в файле можно методом SaveToFile, в который передается имя файла. Если указанный файл не может быть создан или открыт для записи, генерируется исключение EFCreateError.


Таким образом, работа с потоком, связанным с файлом, может быть организована следующим образом:


var T: TMemoryStream;

...

Т:= TMemoryStream.Create;

Т.LoadFromFile('f.dat');

// операторы изменения данных

Т.SaveToFile('f.dat');

FreeAndNil(T);


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


Т.LoadFromFile('f1.dat');

Т.SaveToFile('f2.dat');


Для объединения нескольких файлов можно создать дополнительный поток, загружать в него поочередно различные файлы и присоединять их данные в конец первого потока описанным ранее методом CopyFrom, или методом SaveToStream. Этот метод копирует все данные из потока, созданного в памяти, в другой указанный поток, начиная с его текущей позиции Position.


Ниже приведен пример создания файла f3.dat, объединяющего файлы f.dat, f1.dat и f2.dat.


var T, T1: TMemoryStream;

...

Т:= TMemoryStream.Create;

T1:= TMemoryStream.Create;

Т1.LoadFromFile('f.dat');

Т.CopyFrom(T1, 0);

T1.LoadFromFile('f1.dat');

T.CopyFrom(T1, 0);

T1.LoadFromFile('f2.dat');

T.CopyFrom(T1, 0);

T.SaveToFile('f3.dat');

FreeAndNil(T);

FreeAndNil(T1);


В этом примере использован метод CopyFrom. В поток Т1 поочередно загружаются данные различных файлов. Поскольку метод CopyFrom сдвигает текущую позицию потока Т на конец данных, скопированных из потока Т1, следующий вызов CopyFrom копирует новые данные в конец потока, т.е. объединяет прежние и новые данные. В заключение методом SaveToFile данные потока Т сохраняются в файле f3.dat. Если этот файл ранее не существовал, он создается. Если же он существовал, то его прежнее содержимое стирается и заменяется новым.


Вместо метода CopyFrom в предыдущем примере можно было бы использовать метод SaveToStream. Для этого достаточно заменить все операторы


Т.CopyFrom(T1, 0);


операторами

T1.SaveToStream(T);


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: