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 年,UnicodeString
和 string
是一样的:它们是 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 中连接字符串就是这么简单。
但是,就速度而言,在某些情况下您可以对此进行改进。 +
运算符将导致编译器创建代码以创建新的内部字符串数据结构并将 MyString1
和 MyString2
的内容复制到新区域。
因此,例如,如果您想通过连接许多较小的字符串(甚至单个字符)来构建一个大字符串,您可能会通过不使用连续的 +
操作来获得(很多)性能,但不是在开始时分配足够大的结果字符串(使用 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 秒内完成。
我对相当大的字符串执行操作 - 我搜索给定短语的出现,并做各种工作,我们称之为 "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 年,UnicodeString
和 string
是一样的:它们是 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 中连接字符串就是这么简单。
但是,就速度而言,在某些情况下您可以对此进行改进。 +
运算符将导致编译器创建代码以创建新的内部字符串数据结构并将 MyString1
和 MyString2
的内容复制到新区域。
因此,例如,如果您想通过连接许多较小的字符串(甚至单个字符)来构建一个大字符串,您可能会通过不使用连续的 +
操作来获得(很多)性能,但不是在开始时分配足够大的结果字符串(使用 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 秒内完成。