Некоторые обычные операции со строками и динамическими массивами можно делать в 100 раз быстрее.
ReplaceStr
Функции замены подстроки
AnsiReplaceStr
и
AnsiReplaceText
работают
довольно медленно для текстов размером сотни килобайт c тысячами
замен подстроки. Узкое место оказалась в медленном сложении строк
типа
LongText := LongText + ShortText
В этой операции для новой более длинной строки выделяется
необходимое количество памяти. Опыт показал, что обработка текста
значительно ускоряется, если выделять память не тысячи раз, а
всю память заранее, или всего несколько раз. Так работает выделнение
памяти в классе
TList
, но в
AnsiReplaceStr
оптимизация выделения
памяти не используется.
Сначала создадим отдельный класс для хранения строки.
TWideString = class
private
FText: WideString;
LenText: Integer; //index of the last char
CurrentLenText: Integer; //actual length от FText
SpareLen: Integer;
function GetText: WideString;
procedure SetText(const Text: WideString);
public
procedure Append(C: WideString);
property Text: WideString read GetText write SetText;
property Length:Integer read LenText;
end;
function TWideString.GetText: WideString;
begin
CurrentLenText := LenText;
SetLength(FText, CurrentLenText);
{result}GetText := FText;
end;
procedure TWideString.SetText(const Text: WideString);
begin
FText := Text;
LenText := Length(Text);
CurrentLenText := LenText;
end;
procedure TWideString.Append(C: WideString);
var i, LenC: integer;
begin
LenC := Length(C);
if LenText + LenC > CurrentLenText then
begin
SpareLen := max(SpareLen, CurrentLenText);
CurrentLenText := LenText + LenC + SpareLen;
SetLength(FText, CurrentLenText);
end;
for i := 1 to LenC do
begin
inc(LenText);
FText[LenText] := C[i];
end;
end;
Для поиска подстроки требуется функция
PosEx
, но в Delphi7 она неправильно работает со строками
WideString
. Поэтому используем
функцию
WidePosEx
.
function WidePosEx(const SubStr, S: WideString;
Offset: Integer): Integer;
var i, X, Len, LenSubStr, Res: Integer;
begin
if Offset = 1 then Res := Pos(SubStr, S) else
begin
i := Offset;
LenSubStr := Length(SubStr);
Len := Length(S) - LenSubStr + 1;
while i <= Len do
begin
if S[i] = SubStr[1] then
begin
X := 1;
while (X < LenSubStr) and (S[i + X]
= SubStr[X + 1]) do Inc(X);
if (X = LenSubStr) then
begin
Res := i;
{result}WidePosEx := Res;
exit;
end;
end;
Inc(i);
end;
Res := 0;
end;
{result}WidePosEx := Res;
end;
Наконец, можно написать функцию замены подстроки.
function ReplaceStr(const Source, oldStr,
newStr: WideString): WideString;
var PosOfOldStr, PosOfNextSearch, LenOldStr: integer; Res:
TWideString;
begin
LenOldStr := Length(oldStr);
Res := TWideString.Create;
PosOfNextSearch := 1;
repeat
PosOfOldStr := WidePosEx(oldStr, Source, PosOfNextSearch);
if PosOfOldStr = 0 then break;
Res.Append(copy(Source, PosOfNextSearch, PosOfOldStr
- PosOfNextSearch));
Res.Append(newStr);
PosOfNextSearch := PosOfOldStr + LenOldStr;
until PosOfOldStr = 0;
//till end of line
Res.Append(copy(Source, PosOfNextSearch, maxInt));
{result}ReplaceStr := Res.Text;
Res.Destroy;
end;
Для тестирования я использовал похожий на xml текст размером 1Мб.
//usual making long text
t0 := Time;
for i := 1 to 50000 do txt := txt + 'A1="2345" ';
dt0 := Time - t0;
//usual change of substring
t0 := Time;
txt := AnsiReplaceStr(txt, '23', '67');
dt1 := Time - t0;
//fast algorithm
t0 := Time;
txt := ReplaceStr(txt, '45', '89');
dt2 := Time - t0;
В этом примере на моём РС получилось
dt0 = 41 секунда
dt1 = 59 секунд
dt2 = 0.1 секунды
Теперь ясно, что самый обычный способ создания текста в цикле
txt := txt + 'A1="2345" '
медленный, и его можно было бы ускорить, используя
TWideString
. Также можно ускорить функцию
RemoveCrLfTabDSpace.