在Delphi中查找流中的字符串的有效方法

时间:2017-05-27 19:39:11

标签: delphi stream

我已经提出了这个函数来返回Delphi Stream中字符串的出现次数。但是,我怀疑有一种更有效的方法来实现这一点,因为我使用的是“更高级别”的结构(char),而不是在较低的字节/指针级别(我不熟悉)

function ReadStream(const S: AnsiString; Stream: TMemoryStream): Integer;
var
  Arr: Array of AnsiChar;
  Buf: AnsiChar;
  ReadCount: Integer;

  procedure AddChar(const C: AnsiChar);
  var
    I: Integer;
  begin
    for I := 1 to Length(S) - 1 do
      Arr[I] := Arr[I+1];
    Arr[Length(S)] := C;
  end;

  function IsEqual: Boolean;
  var
    I: Integer;
  begin
    Result := True;
    for I := 1 to Length(S) do
      if S[I] <> Arr[I] then
      begin
        Result := False;
        Break;;
      end;
  end;

begin
  Stream.Position := 0;
  SetLength(Arr, Length(S));
  Result := 0;
  repeat
    ReadCount := Stream.Read(Buf, 1);
    AddChar(Buf);
    if IsEqual then
      Inc(Result);
  until ReadCount = 0;
end;

有人可以提供更有效的程序吗?

2 个答案:

答案 0 :(得分:4)

Stream有一个方法可以让你进入内部缓冲区。

您可以使用Memory property获取指向内部缓冲区的指针。

如果您使用的是32位,则愿意放弃已弃用的TMemoryStream并使用TBytesStream代替使用滥用动态数组和AnsiString在32位中共享相同结构的事实 不幸的是Emba破坏了X64中的兼容性,这意味着没有任何理由你无法获得字符串&gt; X64中2GB。

请注意,此技巧将在64位中突破! (见下面的修复)

您可以使用Boyer-Moore string searching

这允许您编写如下代码:

function CountOccurrances(const Needle: AnsiString; const Haystack: TBytesStream): integer;
var
  Start: cardinal;
  Count: integer;
begin 
  Start:= 1;
  Count:= 0;
  repeat
    {$ifdef CPUx86}
    Start:= _FindStringBoyerAnsiString(string(HayStack.Memory), Needle, false, Start);
    {$else}
    Start:= __FindStringBoyerAnsiStringIn64BitTArrayByte(TArray<Byte>(HaySAtack.Memory), Needle, false, Start);
    {$endif}
    if Start >= 1 then begin
      Inc(Start, Length(Needle));
      Inc(Count);
    end;
  until Start <= 0;
  Result:= Count;
end;

对于32位,您必须重写BoyerMoore code才能使用AnsiString;一个微不足道的重写 对于64位,您必须重写BoyerMoore代码以使用TArray<byte>作为第一个参数;一个相对简单的任务。

如果您正在寻找效率,请尝试避免使用pchars的WinAPI呼叫。 c风格的字符串是一个可怕的想法,因为它们没有长度前缀。

答案 1 :(得分:2)

Johan给了你一个关于Boyer-Moore搜索的好答案。 BM很好,如果 你满足于将它用作“黑匣子”,但如果你想了解发生了什么, 在您自己的代码的复杂性和BM实现之间存在一些鸿沟。

您可能会发现探索比您自己的代码更有效的搜索会很有帮助 但不像BM那么复杂。有一种超简单的方法可以做你想要的事情 用指针,PChars等搞砸了

让我们暂时搁置您想要使用TMemoryStream的事实,以及 考虑在另一个字符串SubStr中查找字符串Target的出现次数。

为了提高效率,您要避免的事情是a)重复扫描相同的字符 一遍又一遍地b)复制一个或两个字符串。

自D7以来,Delphi已经包含PosEx函数:

  

函数PosEx(const SubStr,S:string; Offset:Cardinal = 1):整数;   描述   PosEx在S中返回SubStr的索引,从Offset开始搜索。如果偏移量为1(默认值),则PosEx等效于Pos。   如果未找到SubStr,如果Offset大于S的长度,或者Offset小于1,则PosEx返回0.

所以你可以做的是重复调用PosEx,从Offset = 1开始,每次都是 在SubStr Target中查找Offset以增加function ContainsCount(const SubStr, Target : String) : Integer; var i : Integer; begin Result := 0; i := 1; repeat i := PosEx(SubStr, Target, i); if i > 0 then begin Inc(Result); i := i + Length(SubStr); end; until i <= 0; end; var Count : Integer; Target : String; begin Target := 'aa b ca'; Count := ContainsCount('a', Target); writeln(Count); readln; end. 以跳过它,就像这样(在控制台应用程序中):

PosEx

ContainsCountSubStr都通过TargetContainsCount的事实 我认为不涉及字符串复制,这应该是显而易见的 PosEx永远不会再次扫描相同的字符。

一旦你对自己的工作感到满意,你就可以追踪 进入StrPos以了解它是如何做到的。

您可以使用RTL函数AnsiStrPos / function MemoryStreamToString(M: TMemoryStream): string; begin SetString(Result, PChar(M.Memory), M.Size div SizeOf(Char)); end;

在PChars上以类似的方式执行某些操作

要将内存流转换为字符串,可以使用此代码 Rob Kennedy对q Converting TMemoryStream to 'String' in Delphi 2009

的回答
AcquireTokenSilentAsync

(请注意他在回答后面对替代版本所说的内容)

顺便说一句,如果你查看VCL + RTL代码,你会发现很多字符串解析和处理代码(例如在TParser,TStringList,TExpressionParser中)都可以与PChars一起工作。当然,这是有原因的,因为它最大限度地减少了字符复制,并且意味着大多数扫描操作都可以通过改变指针(PChar)值来完成。