Delphi 中大字符串的安全连接

Safe concatenation of large string in Delphi

我对相当大的字符串执行操作 - 我搜索给定短语的出现,并做各种工作,我们称之为 "database"(我准备一个包含数据的文件以便在 R 中进一步处理)正确使用两个过程/函数:Pos 和StringReplace。它们中的大多数大小约为 20-30 mb,有时更大。

来自文档 here - 我知道所有声明为 "String" 的字符串,例如:

my_string : String;

是"Note: In RAD Studio, string is an alias for UnicodeString"。这意味着我不必担心它们的大小或内存分配,因为 RAD 会自动完成。当然,在这个阶段我可以问一个问题 - 你认为声明的选择对编译器很重要并且会影响字符串的行为,因为它们在技术上是相同的?

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

它对大小和分配、长度等有一定意义(我们说的是超过 20 MB 的丁字裤)?

现在最重要的问题 - 如何安全地将两个大字符串相互组合?内存泄漏和字符串内容安全,程序速度安全等。这里有两个选项:

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

其中文档 here and here 指出这是在 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 中是相同的?

预先感谢您的所有提示。

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

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

在幕后,字符串变量是指向字符串数据结构的指针,字符串是引用计数的并使用写时复制语义。尽管您很可能不需要详细信息,但它们是 documented.

2009 年 Delphi 之前,字符串不是 Unicode:每个字符使用一个字节,因此只有 255 个非空字符可用,由当前代码页决定。那是一段艰难的时光。

在 Delphi 2009 及之后的版本中,字符串是 Unicode 字符串,每个字符有两个字节。因此,现在可以毫不费力地对像“∑γ + ∫sin²x dx”这样的字符串进行编码,而且你永远不需要担心代码页。

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

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

嗯,在 Delphi 2009 年,UnicodeStringstring 是一样的:它们是 Unicode 字符串,每个字符有两个字节。但是,AnsiString 是旧的(遗留的,2009 年之前的)字符串类型,每个字符使用一个字节(最多 255 个非空字符)并且取决于代码页。尝试将“∑γ + ∫sin²x dx”存储在 AnsiString!

And now the most important question - how to safely combine two large strings with each other? Safe for memory leaks and string content, secure for program speed, etc.

要在 Delphi 中组合两个字符串,您几乎总是使用 + 运算符:MyString1 + MyString2。这在正确性、内存管理等方面是 100% 安全的。不会有任何内存泄漏。在 Delphi 中连接字符串就是这么简单。

但是,就速度而言,在某些情况下您可以对此进行改进。 + 运算符将导致编译器创建代码以创建新的内部字符串数据结构并将 MyString1MyString2 的内容复制到新区域。

因此,例如,如果您想通过连接许多较小的字符串(甚至单个字符)来构建一个大字符串,您可能会通过不使用连续的 + 操作来获得(很多)性能,但不是在开始时分配足够大的结果字符串(使用 SetLength 和字符数)并手动将 characters/strings 复制到它(例如,使用 Move byte count).

注意我强调了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 之前和 post-2009 Delphi 版本中均有效,假设字符串声明为 string。 Delphi2009年之前,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 秒内完成。