Сохраняем записи в потоке
Сохраняем записи в потоке
Сохраняем записи в потоке Автор: Dynamic Сохраняем записи в потоке Хранение записей (record) в потоке (stream) или в типизированном файле не представляет особых трудностей даже для начинающих программистов, если размер всех полей записи известен еще на этапе компиляции, т.е. если поля имеют такие типы как Integer, Boolean, ShortString. Полем такой записи может быть даже другая запись, удовлетворяющая указанному условию. Но что делать, если необходимо хранить в файле записи с длинными строками, а подключать какую-либо базу данных нецелесообразно из соображений переносимости программы и уменьшения размера выходного EXE-файла? Вот тут нам на помощь приходят файловые потоки. Файловый поток (FileStream) представляет собой поток данных, по которому можно перемещаться в процессе чтения или записи и манипулирует дисковым файлом, находящимся на локальном или сетевом диске. В библиотеке VCL Дельфи этот механизм реализован в классе TFileStream. Ниже представлены некоторые основные его свойства и методы: * property Size - размер потока; * property Position - текущая позиция в потоке; * procedure Seek - процедура изменения текущей позиции в потоке; * procedure ReadBuffer - процедура чтения данных из потока в буфер; * procedure WriteBuffer - процедура записи данных в поток из буфера; Перед началом работы с потоком его надо создать: var FS: TFileStream; begin FS := TFileStream.Create(FileName, fmOpenRead); ... end; Из примера видно, что конструктор потока принимает на входе 2 параметра: имя файла, с которым будет ассоциирован поток, и режим открытия файла. Полный список различных режимов можно посмотреть в исходном тексте модуля SysUtils или справке Дельфи, для нас же пока представляют интерес 2 режима: * fmCreate - создать файл; * fmOpenRead - открыть файл для чтения. Для примера создадим простую запись: type TMyRec = record rInt: integer; rStr: string; end; Первой рассмотрим процедуру записи: procedure WriteRecord(const FileName: string; Rec: TMyRec); В процедуру передается 2 параметра: имя файла и переменная созданного нами ранее типа. Код: procedure WriteRecord(const FileName: string; Rec: TMyRec); begin FS := TFileStream.Create(FileName, fmCreate); // создаем файл with FS do try Seek(0, soFromBeginning); // перемещаем указатель в начало потока WriteBuffer(Rec.rInt, SizeOf(Integer));// записываем целое _WriteStr(Rec.rStr); // записываем строку finally Free; // закрываем файл, освобождаем память end; Вторым параметром в метод Seekпередается указание перемещать потоковый указатель от начала потока, существуют следующие варианты (определены в модуле Classes): * soFromBeginning - от начала; * soFromCurrent - от текущей позиции; * soFromEnd - от конца. В процедуру WriteBuffer передается сам буфер и его размер. Размер ( Integer) равен четырем байтам, в принципе можно было бы напрямую указать это число, но сами разработчики Дельфи не советуют использовать в проектах прямое указание размеров стандартных типов из-за соображений совместимости, так как в будущих версиях Дельфи он (размер) может быть изменен. На скорости выполнения программы вычисление выражения SizeOf(Integer) никак не отразится, так как оно производится на этапе компиляции. Самое интересное происходит в подпрограмме записи строки в поток. Тут необходимо сделать небольшое отступление. Длина строки, которую мы хотели бы сохранить, может быть как угодно большой, ограничиваясь только объемом доступной оперативной памяти, на момент записи мы ее знаем. Но при чтении в процедуру ReadBuffer также необходимо передать количество считываемых байт и позаботиться об этом придется нам самим. Для этого перед строкой мы должны записать в поток ее текущий размер, впоследствии при чтении мы сможем легко получить это значение. Для удобства подпрограмму записи строки мы опишем в теле главной функции, то есть объявим локальную процедуру. Вот как будет выглядеть код целиком: procedure WriteRecord(const FileName: string; Rec: TMyRec); var FS: TFileStream; procedure _WriteStr(const S: string); var l: integer; begin with FS do begin l := Length(S); // вычисляем длину строки WriteBuffer(l, SizeOf(Integer)); // пишем длину в поток if l > 0 then // если есть что писать - WriteBuffer(S[1], l); // пишем end; begin FS := TFileStream.Create(FileName, fmCreate); // создаем файл with FS do try Seek(0, soFromBeginning); // перемещаем указатель в начало потока WriteBuffer(Rec.rInt, SizeOf(Integer));// записываем целое _WriteStr(Rec.rStr); // записываем строку finally Free; // закрываем файл, освобождаем память end; Процедура чтения структуры мало чем отличается от процедуры ее записи: procedure ReadRecord(const FileName: string; var Rec: TMyRec); var FS: TFileStream; function _ReadStr: string; var l: integer; begin Result := ??; with FS do begin ReadBuffer(l, SizeOf(Integer)); // читаем длину if l > 0 then // если что-то писАли begin SetLength(Result, l); // устанавливаем длину строки ReadBuffer(Result[1], l); // читаем строку end; begin // открываем для чтения FS := TFileStream.Create(?teststream.txt?, fmOpenRead); with FS do try Seek(0, soFromBeginning); // начинаем сначала ReadBuffer(Rec.rInt, SizeOf(Integer)); // читаем целое Rec.rStr := _ReadStr; // читаем строку finally Free; end; Длинная строка Дельфи, по сути, является динамическим массивом, то есть SizeOf(VarStr) = SizeOf(Pointer), причем перед началом собственно массива символов расположены скрытые 8 байт дополнительной служебной информации: 4 байта - длина строки без завершающего нуля, 4 байта - счетчик ссылок. Исходя из этого, при использовании строки в качестве буфера (см. пример) необходимо явно указывать адрес ее первого символа, то есть VarStr[1] или PChar(VarStr)[0]. Также одной из наиболее распространенных ошибок начинающих является то, что перед чтением строки из потока они забывают выделить для нее память процедурой SetLength.