On a side note, for my websockets libarary I've written a function that reads a stream until a pattern is found, you could simply reuse that function from me:
https://github.com/Warfley/LazWebsockets/blob/master/src/wsutils.pas#L268A little bit off topic: Dependening on how much you want to read, with this method of reading (i.e. appending each char) you can run into performance issues pretty fast. The reason being is that Result += char is actually equivalent to:
SetLength(Result, Result.length + 1);
Result[Result.Length] := char;
And SetLength is rather runtime expensive (especially if the heap is very fragmented). Alternatives would be buffered reads (i.e. reading into a fixed size buffer and writing this buffer as a whole to the string) or geometric size increase (as TList does, i.e. increase the memory usage when needed by a factor of 2).
Simple experiment:
program Project1;
{$mode objfpc}{$H+}
uses
Classes, sysutils;
function readStr(str: TStream; len: SizeInt): String;
begin
Result := '';
while len > 0 do
begin
Result += chr(str.ReadByte);
dec(Len);
end;
end;
function readStrBuffered(str: TStream; len: SizeInt): String;
procedure appendToString(var str: String; const buff; buffLen: SizeInt); inline;
var
oldLen: Integer;
begin
oldLen := str.length;
SetLength(str, oldLen + buffLen);
Move(buff, str[oldLen + 1], buffLen);
end;
var
buff: Array[0..1023] of Byte;
i: SizeInt;
begin
i:=0;
Result := '';
while len > 0 do
begin
buff[i] := str.ReadByte;
Inc(i);
if i >= Length(buff) then
begin
appendToString(Result, buff[Low(buff)], Length(buff));
i := 0;
end;
dec(Len);
end;
appendToString(Result, buff[low(buff)], i);
end;
function readStrGeo(str: TStream; len: SizeInt): String;
var
capacity: SizeInt;
resLen: SizeInt;
c: Char;
begin
capacity := 32;
resLen := 0;
SetLength(Result, capacity);
while len > 0 do
begin
c := chr(str.ReadByte);
if resLen >= capacity then
begin
capacity *= 2;
SetLength(Result, capacity);
end;
Result[resLen + 1] := c;
Inc(resLen);
dec(Len);
end;
SetLength(Result, resLen);
end;
var str: TStringStream;
start, extendTime, buffTime, geoTime: LongWord;
s1, s2, s3, s: String;
begin;
str := TStringStream.Create('');
while not eof do
begin
ReadLn(s);
str.Write(s[1], s.Length);
end;
str.Seek(0, 0);
start := GetTickCount64;
s1 := readStr(str, str.Size);
extendTime := GetTickCount64 - start;
str.Seek(0, 0);
start := GetTickCount64;
s2 := readStrBuffered(str, str.Size);
buffTime := GetTickCount64 - start;
str.Seek(0, 0);
start := GetTickCount64;
s3 := readStrGeo(str, str.Size);
geoTime := GetTickCount64 - start;
WriteLn(extendTime);
WriteLn(buffTime);
WriteLn(geoTime);
end.
On around 2 megabytes of data simply appending characters takes around 100 ms, reading via a buffer takes around 30ms (i.e. a factor 3 faster) and reading using geometric size increase takes around 15 ms (i.e. a factor 6 faster). This is of course without any memory fragmentation, because this program does nothing else than this job. So the numbers are rather optimistic, and they scale very differently.
For less than 4 k the buffered read only needs a single resize, which is much better than any other option while on very large (mutiple mb) texts the geometic increase will outperform the others even more. But the geometric increase takes in the worst case 2 times the memory the string actually takes, while the buffered approach only adds a flat the buffer size (here) 4k.
If you are only reading strings of a few dozend bytes, your approach should be fast enough, but keep in mind if you are doing this really often, or you are reading long strings, this is not a very efficient way of doing so