String 为什么将字符串保存到文件的方式会影响结果

String 为什么将字符串保存到文件的方式会影响结果,string,delphi,unicode,localization,String,Delphi,Unicode,Localization,我已经解决了将包含德语字母的字符串保存到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); writ

我已经解决了将包含德语字母的字符串保存到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
。这是什么原因

追加

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

对于
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
方法

对于
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?)


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

AssignFile(tf, 'b:\tmp.txt',CP_UTF8);
要针对不同的区域设置保存文件,也可以先在文件中放置BOM表:

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

我相信如果他们能完成这项工作,他们会一直使用RTL中的函数。在本例中,TStringList以一种非常简单的方式为您实现了这一技巧:

在这个小示例中,我将一个stringlist保存到一个文本文件中,然后再次加载它。为了证明它有效,我在再次加载文本文件后添加了一个断言测试

因此,无需使用
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;

在这种情况下,请尝试使用
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;

瞧,谢谢,@LU-RD。我有德尔福XE5。你的评论帮助了我。代码使用以下行正常工作:
assignfile(tf'b:\tmp.txt',CP\u UTF8)请发布一个答案,这样我就可以接受了。我想你需要决定的是你希望如何编码你的文本。“你应该积极地做出决定。谢谢你,@DavidHeffernan。”。我是否正确理解将字符串变量写入文件需要明确的编码指示?但是,这是否意味着编写一个包含
记录的
文件,其中包含
字符串
(固定长度)的
记录将导致数据损坏?一个简单的问题。您的文本要使用什么编码。谢谢。这是否意味着
TMemoryStream.SaveToFile
的写入方法与
Write
的写入方法不同?是的,TMemoryStream将文件存储在UTF-16中,而本例将文件存储在UTF-8中。这是一个相当糟糕的建议。由于UTF-8的数据单元适合单字节、字节顺序标记编码,Unicode Consortium不鼓励使用它。@FreeConsulting,请注意,我使用了“can”一词。在某些情况下,对于UTF-8文件,BOM是更好的选择。@LURD,当然可以。但我不同意你使用它的理由。UTF-8编码的文件已经与任何语言环境兼容,无论是否存在BOM。包含或排除BOM的要求仅仅是支持旧的和/或损坏的软件实现的问题。非常有必要提及您的示例编写的UTF-16 LE(和未指定的读取)编码。谢谢,@JensBorrisholt。对不起,我不能接受超过一个答案,我只能投赞成票。我注意到文件的大小不同。如果它是由
TMemoryStream
或调用
AssignFile(tf,'b:\tmp.txt',CP_UTF8)后由
Write
编写的大小为22字节。我认为一个11个字符长的字符串应该有22个字节的大小+2个字节,不是吗?在使用TStringList写入之后,它是28个字节。如果我用记事本打开这些文件,它们将显示相同的字符串。这是否意味着,如果文件是用
TStringList
写入的,那么它也必须由
TStringList
读取?@JensBorrisholt,OK,不是“未指定”,而是“使用
前导码检测到的”。正如您所看到的,由于没有清楚地指出所使用的编码,您给OP带来了更多的混乱。@asd tm没关系。额外字节的原因是我使用Unicode而不是UTF8保存它。如果要将其保存在UTF8中,请将TEncoding.Unicode替换为TEncoding.UTF8加载时我不指定编码的原因是因为字符串列表自动检测编码谢谢,Remy Lebeau。我已经用你的代码生成的十六进制附加了我的帖子。所有的答案都非常有用,不幸的是我只能接受一个。我只能投你宝贵的一票。
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;
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;