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

Не сильно преувеличу, если скажу, что строки являются одной из основных жемчужин языка 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


Опубликовал admin
12 Июн, Суббота 2004г.



Программирование для чайников.