我已经提出了这个函数来返回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;
有人可以提供更有效的程序吗?
答案 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
ContainsCount
和SubStr
都通过Target
和ContainsCount
的事实
我认为不涉及字符串复制,这应该是显而易见的
PosEx
永远不会再次扫描相同的字符。
一旦你对自己的工作感到满意,你就可以追踪
进入StrPos
以了解它是如何做到的。
您可以使用RTL函数AnsiStrPos
/ function MemoryStreamToString(M: TMemoryStream): string;
begin
SetString(Result, PChar(M.Memory), M.Size div SizeOf(Char));
end;
要将内存流转换为字符串,可以使用此代码 Rob Kennedy对q Converting TMemoryStream to 'String' in Delphi 2009
的回答AcquireTokenSilentAsync
(请注意他在回答后面对替代版本所说的内容)
顺便说一句,如果你查看VCL + RTL代码,你会发现很多字符串解析和处理代码(例如在TParser,TStringList,TExpressionParser中)都可以与PChars一起工作。当然,这是有原因的,因为它最大限度地减少了字符复制,并且意味着大多数扫描操作都可以通过改变指针(PChar)值来完成。