【问题标题】:Faster way to split text in Delphi TStringList在 Delphi TStringList 中分割文本的更快方法
【发布时间】:2025-12-01 15:30:02
【问题描述】:

我有一个应用程序需要在 TStringList 中进行繁重的文本操作。基本上我需要用分隔符分割文本;例如,如果我有一个包含 1000 个字符的单行并且此分隔符在此行中出现 3 次,那么我需要将其拆分为 3 行。分隔符可以包含多个字符,例如可以是一个标签,例如 '[test]'。

我已经编写了两个函数来使用两种不同的方法来完成这项任务,但是在大量文本(通常超过 2mbytes)中两者都很慢。

我怎样才能以更快的方式实现这个目标?

这两个函数都接收 2 个参数:'lines' 是原始 tstringlist 和 'q' 是分隔符。

function splitlines(lines : tstringlist; q: string) : integer;
var
  s, aux, ant : string;
  i,j : integer;
  flag : boolean;
  m2 : tstringlist;
begin
  try
    m2 := tstringlist.create;
    m2.BeginUpdate;
    result := 0;
    for i := 0 to lines.count-1 do
    begin
      s := lines[i];
      for j := 1 to length(s) do
      begin
        flag := lowercase(copy(s,j,length(q))) = lowercase(q);
        if flag then
        begin
          inc(result);
          m2.add(aux);
          aux := s[j];
        end
        else
          aux := aux + s[j];
      end;
      m2.add(aux);
      aux := '';
    end;
    m2.EndUpdate;
    lines.text := m2.text;
  finally
    m2.free;
  end;
end;


function splitLines2(lines : tstringlist; q: string) : integer;
var
  aux, p : string;
  i : integer;
  flag : boolean;
begin
  //maux1 and maux2 are already instanced in the parent class
  try
    maux2.text := lines.text;
    p := '';
    i := 0;
    flag := false;
    maux1.BeginUpdate;
    maux2.BeginUpdate;
    while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
    begin
      flag := true;
      aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
      maux1.add(aux);
      maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
      p := copy(maux2.text,1,1);
      maux2.text := copy(maux2.text,2,length(maux2.text));
      inc(i);
    end;
  finally
    result := i;
    maux1.EndUpdate;
    maux2.EndUpdate;
    if flag then
    begin
      maux1.add(p+maux2.text);
      lines.text := maux1.text;
    end;
  end;
end;

【问题讨论】:

  • 问题是我的分隔符有多个字符,例如,它可以是一个完整的单词。
  • 包括所有相关要求。顺便说一句,将 try 放在构造函数调用之后。
  • 你可能会发现我对这个问题的回答很有用:*.com/questions/15424293/…

标签: delphi split delphi-2007 tstringlist


【解决方案1】:

我没有测试过速度,但出于学术目的,这里有一个简单的分割字符串的方法:

myStringList.Text :=
  StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case

当您设置TStringListText 属性时,它会解析新行并在那里拆分,因此转换为字符串,用新行替换分隔符,然后将其分配回Text 属性有效。

【讨论】:

  • 伙计,永远感谢你!您刚刚使我的应用程序变得更好! :D
  • @Marcus Adams IIRC,当字符串大小超过几兆字节时,Unicode Delphi 中的 StringReplace(即未启用 FastCode)非常慢。
  • @XichenLi:那么这个问题的标签包含'delphi-2007'是一件好事:-)
  • @KenWhite 确实如此。 (PS:如果分隔符只有一个字符,空间可以换时间,即使使用unicode Delphi。:D)
  • 您还可以使用TStringListDelimiterStrictDelimiterDelimitedText 属性。
【解决方案2】:

您的代码的问题(至少是第二种方法)是

  • 你一直在使用小写,如果调用这么多次会很慢
  • 如果我没看错,您将把剩余的全部文本复制回原始来源。对于大字符串(例如文件)来说,这肯定会特别慢

我的库中有一个分词器。它不是最快或最好的,但应该可以(您可以从Cromis Library 获得它,只需使用单位 Cromis.StringUtils 和 Cromis.Unicode):

type
  TTokens = array of ustring;

  TTextTokenizer = class
  private
    FTokens: TTokens;
    FDelimiters: array of ustring;
  public
    constructor Create;
    procedure Tokenize(const Text: ustring);
    procedure AddDelimiters(const Delimiters: array of ustring);
    property Tokens: TTokens read FTokens;
  end;

{ TTextTokenizer }

procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
  I: Integer;
begin
  if Length(Delimiters) > 0 then
  begin
    SetLength(FDelimiters, Length(Delimiters));

    for I := 0 to Length(Delimiters) - 1 do
      FDelimiters[I] := Delimiters[I];
  end;
end;

constructor TTextTokenizer.Create;
begin
  SetLength(FTokens, 0);
  SetLength(FDelimiters, 0);
end;

procedure TTextTokenizer.Tokenize(const Text: ustring);
var
  I, K: Integer;
  Counter: Integer;
  NewToken: ustring;
  Position: Integer;
  CurrToken: ustring;
begin
  SetLength(FTokens, 100);
  CurrToken := '';
  Counter := 0;

  for I := 1 to Length(Text) do
  begin
    CurrToken := CurrToken + Text[I];

    for K := 0 to Length(FDelimiters) - 1 do
    begin
      Position := Pos(FDelimiters[K], CurrToken);

      if Position > 0 then
      begin
        NewToken := Copy(CurrToken, 1, Position - 1);

        if NewToken <> '' then
        begin
          if Counter > Length(FTokens) then
            SetLength(FTokens, Length(FTokens) * 2);

          FTokens[Counter] := Trim(NewToken);
          Inc(Counter)
        end;

        CurrToken := '';
      end;
    end;
  end;

  if CurrToken <> '' then
  begin
    if Counter > Length(FTokens) then
      SetLength(FTokens, Length(FTokens) * 2);

    FTokens[Counter] := Trim(CurrToken);
    Inc(Counter)
  end;

  SetLength(FTokens, Counter);
end;

【讨论】:

    【解决方案3】:

    只使用 JCL 库中的 StrTokens 怎么样

    procedure StrTokens(const S: string; const List: TStrings);

    它是开源的 http://sourceforge.net/projects/jcl/

    【讨论】:

      【解决方案4】:

      作为附加选项,您可以使用正则表达式。 Delphi 的最新版本(XE4 和 XE5)带有内置的正则表达式支持;旧版本可以在Regular-Expressions.info 找到免费的regex library download (zip file)

      对于内置的正则表达式支持(使用通用的TArray&lt;string&gt;):

      var
        RegexObj: TRegEx;
        SplitArray: TArray<string>;
      begin
        SplitArray := nil;
        try
          RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
          SplitArray := RegexObj.Split(Lines, 0);
        except
          on E: ERegularExpressionError do begin
          // Syntax error in the regular expression
          end;
        end;
        // Use SplitArray
      end;
      

      在早期的 Delphi 版本中使用 TPerlRegEx:

      var
        Regex: TPerlRegEx;
        m2: TStringList;
      begin
        m2 := TStringList.Create;
        try
          Regex := TPerlRegEx.Create;
          try
            Regex.RegEx := '\[test\]';  //  Using your sample expression - replace with q
            Regex.Options := [];
            Regex.State := [preNotEmpty];
            Regex.Subject := Lines.Text;
            Regex.SplitCapture(m2, 0);
          finally
            Regex.Free;
          end;
          // Work with m2
        finally
          m2.Free;
        end;
      end;
      

      (对于那些不知道的人,使用示例表达式中的\ 是因为[] 字符在正则表达式中是有意义的,需要转义才能在正则表达式文本中使用。通常,它们不是必需的在正文中。)

      【讨论】:

        最近更新 更多