【问题标题】:Why does the way of saving a string to a file affect results为什么将字符串保存到文件的方式会影响结果
【发布时间】:2015-11-24 12:10:26
【问题描述】:

我在将包含德语字母的字符串保存到 txt 文件时遇到了问题。 MCVE 如下所示:

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string; //alias for UnicodeString 
  tf: textfile;
  ms: tmemorystream;
begin
  s := 'ßüÜöÖäÄФфшШ';
  assignfile(tf, 'b:\tmp.txt');
  Rewrite(tf);
  write(tf, s);
  closefile(tf);
  ms := tmemorystream.Create;
  try
    ms.WriteBuffer(Pointer(s)^, Length(s) * SizeOf(s[Low(s)]));
    ms.Position := 0;
    ms.SaveToFile('b:\tmp2.txt');
  finally
    ms.Free;
  end;
end;

如果字符串直接保存到文件中,我们会得到以下信息:tmp.txt – ?uUoOaAФфшШ。尽管西里尔字母仍然存在,但德语字母已更改。如果字符串由 TMemoryStream 保存,则结果是正确的:tmp2.txt – ßüÜöÖäÄФфшШ。这是什么原因?

附加

我决定为以不同方式保存的给定字符串添加 HEX 值:

对于Write 方法:

data: array[0..10] of byte = (
    $3F, $75, $55, $6F, $4F, $61, $41, $D4, $F4, $F8, $D8
);

对于在AssignFile(tf, 'b:\tmp.txt',CP_UTF8); 之后调用的Write 方法:

data: array[0..21] of byte = (
    $C3, $9F, $C3, $BC, $C3, $9C, $C3, $B6, $C3, $96, $C3, $A4, $C3, $84, $D0, $A4, 
    $D1, $84, $D1, $88, $D0, $A8
);

对于TMemoryStream:

data: array[0..21] of byte = (
    $DF, $00, $FC, $00, $DC, $00, $F6, $00, $D6, $00, $E4, $00, $C4, $00, $24, $04, 
    $44, $04, $48, $04, $28, $04
);

对于TStringList:

data: array[0..27] of byte = (
        $FF, $FE, $DF, $00, $FC, $00, $DC, $00, $F6, $00, $D6, $00, $E4, $00, $C4, $00, 
        $24, $04, $44, $04, $48, $04, $28, $04, $0D, $00, $0A, $00
    );

附加

根据@Remy-Lebeau 的宝贵建议: 此方法生成一个 25 字节长的文件。这与在AssignFile(tf, 'b:\tmp.txt',CP_UTF8); 之后调用的 Write 方法生成的 HEX 类似,额外增加了 3 个字节(BOM?)。

data: array[0..24] of byte = (
    $EF, $BB, $BF, $C3, $9F, $C3, $BC, $C3, $9C, $C3, $B6, $C3, $96, $C3, $A4, $C3, 
    $84, $D0, $A4, $D1, $84, $D1, $88, $D0, $A8
);

【问题讨论】:

  • 谢谢@LU-RD。我有德尔福 XE5。你的评论帮助了我。该代码与以下行正常工作:assignfile(tf, 'b:\tmp.txt',CP_UTF8); 请发布答案,以便我接受。
  • 我猜你需要决定的是你希望如何编码你的文本。你应该积极地做出这个决定。
  • 谢谢你,@DavidHeffernan。我是否正确理解将字符串变量写入文件需要明确指示编码?但这是否意味着编写包含records 和strings(固定长度)的File of somerecord 会导致数据损坏?
  • 简单问题。你想为你的文本使用什么编码。

标签: string delphi unicode localization


【解决方案1】:

要使用 Write/WriteLn 过程将 unicode 字符串存储在文本文件中,您必须首先分配正确的代码页:

AssignFile(tf, 'b:\tmp.txt',CP_UTF8);

要针对不同的语言环境保存文件,您也可以在文件中首先放置 BOM:

Write(tf, #$FEFF);  // An utf8 BOM

【讨论】:

  • 谢谢。这是否意味着TMemoryStream.SaveToFile的写法与Write的写法不同?
  • 是的,TMemoryStream 以 UTF-16 存储文件,而本示例以 UTF-8 存储文件。
  • 这是相当糟糕的建议。由于 UTF-8 的数据单元适合一个字节,字节顺序标记is redundant with UTF-8 编码,Unicode 联盟不鼓励使用它。
  • @FreeConsulting,请注意我使用了“可以”这个词。在某些情况下,BOM 是 UTF-8 文件的更好选择。
  • @LURD,当然。但我不同意你使用它的理由。 UTF-8 编码文件已经与任何语言环境兼容,无论 BOM 是否存在。包含或排除 BOM 的要求仅仅是支持旧的和/或损坏的软件实现。
【解决方案2】:

如果他们能完成这项工作,我相信他们总是使用 RTL 中的函数。在这种情况下,TStringList 以一种非常简单的方式为您解决问题:

在这个小例子中,我将一个字符串列表保存到一个文本文件并再次加载它。为了证明它有效,我在 Iøve 再次加载文本文件后添加了一个 Assert 测试。

所以不需要使用MemoryStream 和关于BOM。使用 TStringList,因为它具有您需要的所有功能。

procedure TForm1.Button1Click(Sender: TObject);
var
  s: String;
begin
  s := 'ßüÜöÖäÄФфшШ';

  with TStringList.Create do
    try
      Text := s;
      SaveToFile('C:\aa\tmp3.txt', TEncoding.Unicode);
    finally
      free;
    end;

  with TStringList.Create do
    try
      LoadFromFile('C:\aa\tmp3.txt');
      Assert(Strings[0] = s, '');
    finally
      free;
    end;
end;

【讨论】:

  • 确实有必要提一下您的示例编写的 UTF-16 LE(并读取未指定)编码。
  • 谢谢你,@JensBorrisholt。对不起,我不能接受一个以上的答案,我只能投票。我注意到文件的大小不同。如果它是由TMemoryStream 或Write 在调用AssignFile(tf, 'b:\tmp.txt',CP_UTF8); 后写入的,则大小为22 字节。我认为一个 11 个字符长的字符串有 22 个字节大小 + BOM 2 个字节,不是吗?用 TStringList 写入后,它是 28 个字节。如果我用记事本打开这些文件,它们会显示相同的字符串。这是否意味着,如果一个文件是用TStringList 写入的,那么它也必须由TStringList 读取?
  • @JensBorrisholt,好的,不是“未指定”,而是“使用 Preamble 检测到”。如您所见,未能明确指出使用的编码,会给 OP 造成更多混乱。
  • @asd-tm 没关系。额外字节的原因是我使用 Unicode 而不是 UTF8 保存它。如果您希望将其保存为 UTF8,请将 TEncoding.Unicode 替换为 TEncoding.UTF8
  • 我加载的时候没有指定编码是因为TStringlist自动检测编码
【解决方案3】:

在这种情况下,请尝试使用TStreamWriter,例如:

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string; //alias for UnicodeString 
  writer: TStreamWriter;
begin
  s := 'ßüÜöÖäÄФфшШ';
  writer := TStreamWriter.Create('b:\tmp.txt', False, TEncoding.UTF8);
  try
    writer.Write(s);
  finally
    writer.Free;
  end;
  ms := TMemoryStream.Create;
  try
    writer := TStreamWriter.Create(ms, TEncoding.UTF8);
    try
      writer.Write(s);
    finally
      writer.Free;
    end;
    ms.Position := 0;
    ms.SaveToFile('b:\tmp2.txt');
  finally
    ms.Free;
  end;
end;

【讨论】:

  • 谢谢你,雷米勒博。我已将我的帖子附加了由您的代码生成的 HEX。所有答案都非常有用,不幸的是我只能接受一个。我只能赞成您的宝贵考虑。
猜你喜欢
  • 1970-01-01
  • 2021-08-27
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
  • 2021-02-23
相关资源
最近更新 更多