Четверг, 02.05.2024
Королевство Delphi
Главное меню
Статьи
Наш опрос
Как часто ви на этот сайт заходите?
Всего ответов: 159
Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Главная » Статьи » Система » Текст

Полет над строкой

Не сильно преувеличу, если скажу, что строки являются одной из основных жемчужин языка Object Pascal. Простые и незаметные, но в то же время такие мощные. Речь, конечно, о длинных строках, объявляемых с помощью типа string (AnsiString). Строки настолько удобны, что их использование часто выходит далеко за рамки работы с текстом. Ну разве не может понравиться возможность склеить два бинарных куска данных простым сложением s1 + s2. В немалой степени таким возможностям способствует поистине гениальное устройство длинной строки. В частности она позволяет хранить любые символы, в том числе нулевые. Замечу, что во многих языках строки представляют собой указатели на первый символ строки, которая должна заканчиваться нулем. Такие строки называются zero-terminated strings. Недостатки такого подхода очевидны: невозможность хранить нулевые символы в строке, медленная работа из-за необходимости сканирования строки для нахождения ее длины, блуждание и порча памяти в случае отсутствия завершающего нуля. Паскалевская строка вместо этого хранит свою длину в виде четырехбайтного значения по отрицательному смещению от начала самой строки. Но при этом еще автоматически подставляет нулевой символ в конце строки! То есть такая строка может выступать и в качестве zero-terminated string с помощью простого приведения типа к указателю на строку PChar. Это часто требуется, например, при вызове функций WinAPI. Возможность хранить в строке любые бинарные данные, гибкость работы с ней и широта использования невольно наводят на мысль использовать строку в качестве хранилища данных. И разработчики Delphi 6 даже включили в стандартный модуль Classes новый потоковый класс - TStringStream. Он весьма похож на TMemoryString, только внутри в качестве хранилища данных потока используется строка. Интерфейс этого класса позволяет легко заполнить поток данными из строки, произвести над ними какие-то операции, и получить обратно данные опять же в виде строки:

var
 ss: TStringStream;
 s: string;
 i: integer;
 r: TRecord;
begin
 s:= '12345';
 i:= $FFFFFFFF;
 ss:= TStringStream.Create( s );
 try
 ss.Position:= 3;
 ss.WriteString( '321' ); // теперь строка имеет вид '123321'
 ss.Write( i, sizeof( i ) ); // теперь в конце еще добавлены 4 символа с кодом FF
 ss.Write( rr, sizeof( rr ); // теперь дописали еще данные некой записи (record)
 s:= ss.DataString; // получаем результирующую строку
 finally
 FreeAndNil( ss );
 end;
end;

Такие вещи и раньше делались с помощью TMemoryString, но теперь это гораздо удобнее из-за отсутствия необходимости приведения типов. Код стал короче и прозрачнее. Но все же есть в классе TStringStream и парочка своих ложек дегтя. Во-первых, довольно странная реализация метода Write, которая не позволяет этому классу быть равнозначной заменой другим наследникам TStream. Дело в том, что данный метод всегда устанавливает размер потока по концу только что записанных данных. Так что имеет смысл только последовательная запись. Попытка переместиться внутри потока и записать небольшой фрагмент данных приведет к "обрезанию" потока по концу этого фрагмента. Во-вторых, далеко не оптимальная реализация механизмов работы с памятью (в частности интенсивное использование функции Length для определения размера потока), что делает класс несколько "тормозным" при активной работе. Такая ситуация сподвигла меня на написание своего аналога TStringStream, лишенного этих недостатков. Его вам и представляю:

unit StrStrm;
 
interface

uses
 Classes;

type
 TStrStream = class(TStream)
 private
 FDataString: string;
 FPosition: integer;
 FSize: integer;
 FMemory: pointer;
 protected
 procedure SetSize(NewSize: Longint); override;
 public
 constructor Create(const AString: string);
 function Read(var Buffer; Count: Longint): Longint; override;
 function ReadString(Count: Longint): string;
 function Seek(Offset: Longint; Origin: Word): Longint; override;
 function Write(const Buffer; Count: Longint): Longint; override;
 procedure WriteString(const AString: string);
 property DataString: string read FDataString;
 end;

implementation

{ TStrStream }

constructor TStrStream.Create(const AString: string);
begin
 inherited Create;
 WriteString(AString);;
 FPosition:= 0;
end;

function TStrStream.Read(var Buffer; Count: Longint): Longint;
begin
 Result := FSize - FPosition;;
 if Result > Count then Result := Count;
 Move((PChar(longword(FMemory) + longword(FPosition)))^, Buffer, Result);
 Inc(FPosition, Result);
end;

function TStrStream.Write(const Buffer; Count: Longint): Longint;
begin
 Result := Count;
 if FPosition + Result > FSize then
 begin
 SetLength(FDataString, (FPosition + Result));
 FSize:= FPosition + Result;
 if FSize > 0 then
 FMemory:= @(FDataString[1]);
 end;
 Move(Buffer, (PChar(longword(FMemory) + longword(FPosition)))^, Result);
 Inc(FPosition, Result);
end;

function TStrStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
 case Origin of
 soFromBeginning: FPosition := Offset;
 soFromCurrent: FPosition := FPosition + Offset;
 soFromEnd: FPosition := FSize - Offset;
 end;
 if FPosition > FSize then
 FPosition := FSize
 else
 if FPosition < 0 then
 FPosition := 0;
 Result := FPosition;
end;

function TStrStream.ReadString(Count: Longint): string;
var
 Len: Integer;
begin
 Len := FSize - FPosition;
 if Len > Count then Len := Count;
 SetString(Result, PChar(longword(FMemory) + longword(FPosition)), Len);
 Inc(FPosition, Len);
end;

procedure TStrStream.WriteString(const AString: string);
begin
 Write(PChar(AString)^, Length(AString));
end;

procedure TStrStream.SetSize(NewSize: Longint);
begin
 if NewSize <> FSize then
 begin
 SetLength(FDataString, NewSize);
 FSize:= NewSize;
 if FSize > 0 then
 FMemory:= @(FDataString[1])
 else
 FMemory:= nil;
 if FPosition > NewSize then FPosition := NewSize;
 end;
end;

end.

Быстрота и удобство этого класса привели к тому, что я использую его практически везде, где раньше требовался TMemoryStream. Если к нему еще добавить методы LoadFrom... и SaveTo..., то замена будет полной. Но эту доработку я оставлю для вашего удовольствия :)

Владимир Волосенков uno@tut.by

Получить ссылку на материал

Категория: Текст | Добавил: Барон (20.12.2011)
Просмотров: 920 | Теги: строка | Рейтинг: 0.0/0
[ Пожертвования для сайта ] [ Пожаловаться на материал ]

Если вам помог материал сайта кликните по оплаченной рекламе размещенной в центре

Поиск
Категории раздела
ActiveX [10]
CORBA и COM [16]
Kol и MCK [23]
WinAPI [28]
Компоненты [27]
Работа с Bluetooth [4]
Железо [8]
Текст [18]
Разное [98]
Королевство Delphi © 2010-2024
Яндекс цитирования