【问题标题】:How do I prevent TStrings.SaveToFile creating a final empty line?如何防止 TStrings.SaveToFile 创建最终的空行?
【发布时间】:2019-10-17 07:23:41
【问题描述】:

我有一个像这样的文件.\input.txt

aaa
bbb
ccc

如果我使用TStrings.LoadFromFile 读取它并使用TStrings.SaveToFile 将其写回(即使没有应用任何更改),它会在输出文件的末尾创建一个空行。

var
  Lines : TStrings;
begin
  Lines := TStringList.Create;
  try
    Lines.LoadFromFile('.\input.txt');

    //...

    Lines.SaveToFile('.\output.txt');
  finally
    Lines.Free;
  end;
end;

使用TStrings.Text 属性可以观察到相同的行为,该属性将返回一个在其末尾包含一个空行的字符串。

【问题讨论】:

  • 只是想知道,即使文件中没有应用任何更改,您到底为什么还要写回它?为什么不直接阅读呢?
  • @BilalAhmed:当然,这是一个简化的测试,在对字符串列表应用更改时会出现相同的空行
  • “创建一个空行”我猜你的意思是你的原始文件不以\n 字符结尾,并且函数将\n 添加到文件中?还是该函数在文件末尾的现有\n 之后直接添加\n? POSIX 要求文本文件的所有行都以\n 结尾,仅供参考。许多软件都是为了遵循某些标准而编写的,所以这就是为什么很多编辑器会在默认情况下保存文件时添加缺少的终止 \n(例如 vim、IDE 等默认情况下都会使您的文件符合 POSIX 标准。)

标签: delphi tstringlist


【解决方案1】:

对于 Delphi 10.1 及更高版本,有一个属性 TrailingLineBreak 控制此行为。

当 TrailingLineBreak 属性为 True(默认值)时,Text 属性将在最后一行之后包含换行符。为假时, 那么文本值在最后一行之后将不包含换行符。这也是 可以通过 soTrailingLineBreak 选项控制。

【讨论】:

  • 很好的信息,我正在开发 Delphi2007 和 DelphiXE7,但我肯定会很高兴在升级 IDE 后立即使用 TrailingLineBreak 属性。 +1 并接受
【解决方案2】:

对于 Delphi 10.1 (Berlin) 或更高版本,Uwe 的回答中描述了最佳解决方案。

对于较旧的 Delphi 版本,我通过创建 TStringList 的子类并覆盖 TStrings.GetTextStr 虚函数找到了解决方案,但我很高兴知道是否有更好的解决方案,或者其他人是否发现了问题我的解决方案

界面:

  uses
    Classes;

  type
    TMyStringList = class(TStringList)
    private
      FIncludeLastLineBreakInText : Boolean;
    protected
      function GetTextStr: string; override;
    public
      constructor Create(AIncludeLastLineBreakInText : Boolean = False); overload;
      property IncludeLastLineBreakInText : Boolean read FIncludeLastLineBreakInText write FIncludeLastLineBreakInText;
    end;

实施:

uses
  StrUtils;      

constructor TMyStringList.Create(AIncludeLastLineBreakInText : Boolean = False);
begin
  inherited Create;

  FIncludeLastLineBreakInText := AIncludeLastLineBreakInText;
end;

function TMyStringList.GetTextStr: string;
begin
  Result := inherited;

  if(not IncludeLastLineBreakInText) and EndsStr(LineBreak, Result)
  then SetLength(Result, Length(Result) - Length(LineBreak));
end;

示例:

procedure TForm1.Button1Click(Sender: TObject);
var
  Lines : TStrings;
begin
  Lines := TMyStringList.Create();
  try
    Lines.LoadFromFile('.\input.txt');
    Lines.SaveToFile('.\output.txt');
  finally
    Lines.Free;
  end;
end;

【讨论】:

  • 值得指出的是,你的代码偶尔会出现SetLength(Result, -2)
  • 在你的GetTextStr 中,如果Length(Result)0,那么你就是SetLength(Result, -2),这很糟糕。效果可能与SetLength(Result, 0) 相同,但我知道对此没有任何保证。至少官方文档不包含任何此类保证。 (所以理论上可能会发生坏事。)
  • 但是现在你还有另一个错误!如果Length(Result) = 1,那么你做SetLength(Result, -1),这同样糟糕!此外,Result 可能没有以换行符结尾,在这种情况下,您将从最后一行中删除最后两个字符。这也是一个错误。 (这可能会发生,例如,如果你使用TrailingLineBreak,我怀疑。即使没有,也可能有其他情况。)你真的应该测试字符串是否真的以换行符结尾,比如if not IncludeLastLineBreakInText and Result.EndsWith(LineBreak) then
  • @AndreasRejbrand:我想当然地认为,在存在任何字符的情况下,TStrings 至少会添加一个 LineBreak,但这种行为将来可能会改变。再次更新答案,谢谢
  • 很抱歉,但新条件仍然错误... :( Pos(LineBreak, Result) = Length(Result) - Length(LineBreak) + 1.Pos 给出第一个匹配项的索引。如果您的字符串包含 6 个换行符,它将给出第一个的位置,但你显然期望最后一个......
猜你喜欢
  • 1970-01-01
  • 2012-09-14
  • 2016-06-16
  • 1970-01-01
  • 1970-01-01
  • 2021-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多