4

我需要遍历一个 HTML 字符串并用 0(零)替换字符,除了标签、空格和换行符。我在下面创建了这段代码,但它太慢了。拜托,有人可以帮我让它更快(优化)吗?

procedure TForm1.btn1Click(Sender: TObject);
var
  Txt: String;
  Idx: Integer;
  Tag: Boolean;
begin
  Tag := False;
  Txt := mem1.Text;
  For Idx := 0 to Length(Txt) - 1 Do
  Begin
    If (Txt[Idx] = '<') Then
      Tag := True Else
    If (Txt[Idx] = '>') Then
    Begin
      Tag := False;
      Continue;
    end;
    If Tag Then Continue;
    If (not (Txt[Idx] in [#10, #13, #32])) Then
      Txt[Idx] := '0';
  end;
  mem2.Text := Txt;
end;

HTML 文本永远不会有“<”或“>”外部标签(在文本中间),所以我不需要担心这一点。

谢谢!

4

3 回答 3

7

这看起来很简单。如果不根据您正在使用的数据分析代码,就很难确定(这总是一个好主意;如果您需要优化 Delphi 代码,请先尝试通过Sampling Profiler运行它,以了解您实际花费的地方你所有的时间,)但如果我不得不做出有根据的猜测,我猜你的瓶颈就在这一行:

Txt[Idx] := '0';

作为编译器对string类型的安全写时复制语义保证的一部分,每次写入字符串的单个元素(字符)都涉及对UniqueString例程的隐藏调用。这可以确保您不会更改其他东西或其他地方持有引用的字符串。

在这种特殊情况下,这不是必需的,因为您在此例程开始时获得了新鲜的字符串,并且您知道它是独一无二的。如果你小心的话,有办法绕过它。

清晰明确的警告:在没有确保你有一个唯一的字符串之前,不要做我将要解释的事情!UniqueString完成此操作的最简单方法是手动 调用。此外,不要在循环期间执行任何可能将此字符串分配给任何其他变量的操作。当我们这样做时,它不会被视为普通字符串。 不注意此警告可能会导致数据损坏。

好的,既然已经解释过了,您可以使用指针直接访问字符串的字符,并绕过编译器的保护措施,如下所示:

procedure TForm1.btn1Click(Sender: TObject);
var
  Txt: String;
  Idx: Integer;
  Tag: Boolean;
  current: PChar; //pointer to a character
begin
  Tag := False;
  Txt := mem1.Text;
  UniqueString(txt); //very important
  if length(txt) = 0 then
    Exit; //If you don't check this, the next line will raise an AV on a blank string
  current := @txt[1];
  dec(current); //you need to start before element 1, but the compiler won't let you
                //assign to element 0
  For Idx := 0 to Length(Txt) - 1 Do
  Begin
    inc(current); //put this at the top of the loop, to handle Continue cases correctly
    If (current^ = '<') Then
      Tag := True Else
    If (current^ = '>') Then
    Begin
      Tag := False;
      Continue;
    end;
    If Tag Then Continue;
    If (not (current^ in [#10, #13, #32])) Then
      current^ := '0';
  end;
  mem2.Text := Txt;
end;

这改变了比喻。我们不是将字符串作为数组索引,而是将其视为磁带,以指针为头部,一次向前移动一个字符,从头到尾扫描,并在适当时更改其下的字符。没有对 的冗余调用UniqueString,也没有重复计算偏移量,这意味着这可以快得多。

使用这样的指针时要非常小心。 编译器的安全检查是有充分理由的,并且在它们之外使用指针步骤。但有时,它们确实可以帮助您加快代码速度。再一次,在尝试这样的事情之前先配置文件。确保你知道是什么让事情变慢了,而不是仅仅认为你知道。如果结果是其他运行缓慢的东西,请不要这样做;而是找到真正问题的解决方案。

于 2013-04-30T04:57:43.160 回答
2

编辑:看起来我错了 -UniqueString不是问题。实际的瓶颈似乎是按字符访问字符串。鉴于我的整个答案无关紧要,我已经完全替换了它。

如果您使用 aPChar来避免重新计算字符串偏移量,同时仍然通过 更新字符串Txt[Idx],则该方法要快得多(在我的 1000 次运行测试中,从 5 秒降至 0.5 秒)。

这是我的版本:

procedure TForm1.btn1Click(Sender: TObject);
var
  Idx: Integer;
  Tag: Boolean;
  p : PChar;
  Txt : string;
begin
  Tag := False;
  Txt := Mem1.Text;
  p := PChar(txt);
  Dec(p);
  For Idx := 0 to Length(Txt) - 1 Do
  Begin
    Inc(p);
    If (not Tag and (p^ = '<')) Then begin
      Tag := True;
      Continue;
    end
    Else If (Tag and (p^ = '>')) Then
    Begin
      Tag := False;
      Continue;
    end;
    If Tag Then Continue;
    If (not (p^ in [#10, #13, #32])) Then begin
      Txt[Idx] := '0';
    end;
  end;
  mem2.Text := Txt;
end;
于 2013-04-30T04:49:01.683 回答
1

我做了一些分析并想出了这个解决方案。

  • 测试> #32而不是[#10,#13,#32]获得一些速度(感谢@DavidHeffernan)。
  • 循环中更好的逻辑也提供了一些额外的速度。
  • 在 a 的帮助下以独占方式访问字符串PChar更有效。

procedure TransformHTML( var Txt : String);
var
  IterCnt : Integer;
  PTxt    : PChar;
  tag     : Boolean;
begin
  PTxt := PChar(Txt);
  Dec(PTxt);
  tag := false;
  for IterCnt := 0 to Length(Txt)-1 do
  begin
    Inc(PTxt);
    if (PTxt^ = '<') then
      tag := true
    else
    if (PTxt^ = '>') then
      tag := false
    else
    if (not tag) and (PTxt^ > #32) then
      PTxt^ := '0';
  end;
end;

该解决方案的效果比 Mason 的解决方案高 30%,比 Blorgbeard 的解决方案高 2.5 倍。

于 2013-05-01T09:06:29.067 回答