在Delphi中安全地连接大字符串

4

我需要对相当大的字符串进行操作——搜索给定短语的出现情况,并使用正确的两个过程/函数Pos和StringReplace在“数据库”(我准备一个带有数据文件以供R进一步处理)上执行各种工作。其中大部分的大小约为20-30 MB,它们有时更大。

这里的文档中可以看到,所有声明为“String”的字符串都具有相同的特性,例如:

my_string : String;

提示:在 RAD Studio 中,string 是 UnicodeString 的别名。这意味着我不必担心字符串的大小或内存分配问题,因为 RAD 会自动处理。当然,在这个阶段,我可能会问一个问题 - 您认为声明的选择对编译器很重要并且会影响字符串的行为吗? 因为它们从技术上讲是相同的?

my_string1 : String;
my_string2 : AnsiString;
my_string3 : UnicodeString;

对于大小和分配、长度等有一定的意义(我们正在谈论20 MB以上的数据传输)?

现在最重要的问题是如何安全地组合两个大字符串?对内存泄漏、字符串内容安全,程序速度快等方面进行保护。这里有两个选项:

> var string1, string2: String;
> ...
> string1 := string1 + string2;

该文档 这里这里 表明这是在Delphi中连接字符串的方式。但还有另一种方式——我可以预先设置一个非常大的字符串大小,并使用移动过程来移动第二个字符串的内容。

const string_size: Integer = 1024*1024;
var string1, string2: String;
    concat_place: Integer = 1;
...
SetLength(string1, string_size);
Move(string2[1],string1[concat_place],Length(string2));
Inc(concat_place,Length(string2));

这似乎更安全,因为该字符串在内存中的区域(大小)不会动态改变,我只需要将适当的值移动到它中。这是一个更好的想法吗?还是有更好的方法?也许我理解错了什么?

另外一个问题是 - 我使用Pos和AnsiPos测试了String和AnsiString搜索,它们在所有组合中似乎都能正常工作。这是否意味着它们现在在Delphi中是相同的?

提前感谢您提供的所有技巧。

1个回答

14

在Delphi中,字符串一直由编译器管理。

实际上,这意味着程序员完全不需要担心它们的内存分配或生命周期,并且不会有(意外的)内存泄漏。除非你开始做非常奇怪的事情,否则字符串的使用与普通整数一样简单和安全。

在底层,字符串变量是指向字符串数据结构的指针,并且字符串是引用计数的,并使用写时复制语义。虽然您很可能不需要了解细节,但它们已经文档化

在Delphi 2009之前,字符串不是Unicode:它们使用每个字符一个字节,因此只有255个非空字符可用,由当前代码页确定。那时候很难受。

在Delphi 2009及更高版本中,字符串是Unicode字符串,每个字符占两个字节。因此,现在可以轻松地编码字符串,例如“∑γ + ∫sin²x dx”,您永远不必担心代码页。

您暗示您相信以下声明是相同的:

MyString1: string;
MyString2: AnsiString;
MyString3: UnicodeString;

在Delphi 2009中,UnicodeString和string是相同的:它们都是每个字符占用两个字节的Unicode字符串。然而,AnsiString是旧的(遗留的,2009年之前的)字符串类型,它使用单个字节表示每个字符(最多255个非空字符),并依赖于代码页。试着将“∑γ + ∫sin²x dx”存储在AnsiString中!
要在Delphi中组合两个字符串,几乎总是使用+运算符:MyString1 + MyString2。这在正确性、内存管理等方面是100%安全的。不会有任何内存泄漏。在Delphi中连接字符串就是这么简单。
但是,在速度方面,有些情况下你可能能够改进这一点。+运算符会导致编译器创建代码来生成一个新的内部字符串数据结构,并将MyString1和MyString2的内容复制到该新区域。
因此,例如,如果您想通过连接许多较小的字符串(甚至单个字符)来构建大字符串,您可能会获得(很多)性能提升,但是不要使用连续的+操作,在开始时分配一个足够大的结果字符串(使用SetLength和字符计数),并手动将字符/字符串复制到其中(例如,使用Movebyte计数)。
请注意,我强调了byte一词:您的示例,
Move(string2[1], string1[concat_place], Length(string2));

可能不会如您所预期地工作。由于字符串被声明为string,在Delphi 2009及更高版本中,它们是Unicode字符串,因此每个字符有两个字节。因此,您需要复制2*Length(string2)个字节。为了安全起见,我会写成

Move(string2[1], string1[concat_place], sizeof(char) * Length(string2));

这段代码适用于2009年之前和之后的Delphi版本,假设字符串声明为string。在Delphi 2009之前,sizeof(char)1;在Delphi 2009及以后的版本中,sizeof(char)2
作为一个简单的基准测试,我尝试了{{待翻译内容}}。
function GetChar: char;
begin
  Result := Char(1 + Random(1000));
end;

const
  N = 100000000;

function MakeString1: string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to N do
    Result := Result + GetChar;
end;

function MakeString2: string;
var
  i: Integer;
begin
  SetLength(Result, N);
  for i := 1 to N do
    Result[i] := GetChar;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  f, c1, c2: Int64;
  dur1, dur2: Double;
  s1, s2: string;
begin

  QueryPerformanceFrequency(f);

  QueryPerformanceCounter(c1);
  s1 := MakeString1;
  QueryPerformanceCounter(c2);
  dur1 := (c2 - c1) / f;

  QueryPerformanceCounter(c1);
  s2 := MakeString2;
  QueryPerformanceCounter(c2);
  dur2 := (c2 - c1) / f;

  ShowMessage(dur1.ToString + sLineBreak + dur2.ToString);

end;

在我的系统上,{{MakeString1}} 完成需要5秒钟,而 {{MakeString2}} 只需要1秒钟。

谢谢,我无法想象有比这更好的解释了 :) 但是,如果我理解正确的话,AnsiString具有Char的一位大小,因此它将适用于AnsiString?即使我在Delphi RAD Studio中编译它? - kwadratens
1
AnsiString每个字符有一个字节(即8位,因此有256个可能的字符)。因此,一个字符等于一个字节。因此,如果string1string2确实是AnsiString类型,则您的代码片段将起作用。 - Andreas Rejbrand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接