unicode文本文件输出在XE2和Delphi 2009之间有所不同?

Tho*_*mas 8 delphi unicode utf-8

当我尝试下面的代码时,与D2009相比,XE2中的输出似乎不同.

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    myByte: Byte;

begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Writeln(Outfile,utf8string('??'));
  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;
Run Code Online (Sandbox Code Playgroud)

在Windows 8 PC上使用XE2进行编译可以使用写字板

?? C

txt十六进制代码:EF BB BF 3F 3F 0D 0A B0 43 0D 0A

在Windows XP PC上使用D2009进行编译可以在Wordpad中进行

总结°C

txt十六进制代码:EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A

我的问题是它为什么不同,我如何使用旧的文本文件I/O将中文字符保存到文本文件?

谢谢!

Rem*_*eau 16

在XE2以后,AssignFile()有一个可选CodePage参数,用于设置输出文件的代码页:

function AssignFile(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
Run Code Online (Sandbox Code Playgroud)

Write()并且Writeln()都有支持UnicodeStringWideChar输入的重载.

因此,您可以创建一个其代码页设置为的文件,CP_UTF8然后Write/ln()在将Unicode字符串写入文件时自动将其转换为UTF-8.

缺点是您将无法再使用AnsiChar值编写UTF-8 BOM ,因为单个字节将转换为UTF-8,因此无法正确写入.您可以通过将BOM编写为单个Unicode字符(它实际上是它 - U+FEFF而不是单独的字节)来解决这个问题.

这适用于XE2:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TextFile;
begin
  AssignFile(Outfile, 'test_chinese.txt', CP_UTF8);
  Rewrite(Outfile);

  //This is the UTF-8 BOM
  Write(Outfile, #$FEFF);

  Writeln(Outfile, '??');
  Writeln(Outfile, '°C');
  CloseFile(Outfile);
end;
Run Code Online (Sandbox Code Playgroud)

话虽如此,如果你想要在D2009和XE2之间更兼容和可靠的东西,请使用TStreamWriter:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TStreamWriter;
begin
  Outfile := TStreamWriter.Create('test_chinese.txt', False, TEncoding.UTF8);
  try
    Outfile.WriteLine('??');
    Outfile.WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

或者手动执行文件I/O:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TFileStream;
  BOM: TBytes;

  procedure WriteBytes(const B: TBytes);
  begin
    if B <> '' then Outfile.WriteBuffer(B[0], Length(B));
  end;

  procedure WriteStr(const S: UTF8String);
  begin
    if S <> '' then Outfile.WriteBuffer(S[1], Length(S));
  end;

  procedure WriteLine(const S: UTF8String);
  begin
    WriteStr(S);
    WriteStr(sLineBreak);
  end;

begin
  Outfile := TFileStream.Create('test_chinese.txt', fmCreate);
  try
    WriteBytes(TEncoding.UTF8.GetPreamble);
    WriteLine('??');
    WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)


Jen*_*off 6

你真的不应该再使用旧的文本I/O.

无论如何,你可以使用TEncoding来获得这样的UTF-8 TBytes:

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    Bytes: TBytes;
    myByte: Byte;
begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Bytes := TEncoding.UTF8.GetBytes('??');
  for myByte in Bytes do begin
    Write(Outfile, AnsiChar(myByte));
  end;

  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;
Run Code Online (Sandbox Code Playgroud)

我不确定是否有更简单的方法将TBytes写入文本文件,也许其他人有更好的主意.

编辑:

对于纯二进制文件(File而不是TextFile类型),可以使用BlockWrite.


Cos*_*und 5

有几个告诉标志可能会告诉你在处理Unicode时出了什么问题.在您的情况下,您?在结果输出文件中看到" ":当您尝试将某些内容从Unicode转换为代码页时,您会收到问号,并且目标代码页不能代表请求的字符.

查看十六进制转储很明显(计算行终止符),问号是将两个中文字符保存到文件的结果.两个字符转换为恰好两个问号.这告诉您Writeln()决定为您提供帮助并将文本从UTF8(unicode表示)转换为您的本地代码页.Delphi团队可能决定这样做,因为旧的I/O例程不应该是UNICODE兼容的; 因为您使用旧的I/O例程编写UTF8字符串,所以它们会通过将其转换为您的代码页来帮助您.你可能不欢迎那帮助,但这并不意味着这样做是错误的:它是无证件的领域.

既然你现在知道为什么会发生这种情况,你知道如何阻止它.让我们WriteLn()知道你发送了一些不需要转换的东西.你会发现这并不是特别容易,因为德尔福XE2显然会"帮助你",无论你是谁.例如,像这样的东西不只是改变字符串类型,它转换为AnsiString,通过代码页转换例程,让你得到问号:

AnsiString(UTF8String('Whatever Unicode'));
Run Code Online (Sandbox Code Playgroud)

因此,如果您需要单线解决方案,您可以尝试转换例程,如下所示:

function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
  N := Length(InStr);
  SetLength(Result, N);
  Move(InStr[1], Result[1], N);
end;
Run Code Online (Sandbox Code Playgroud)

然后你就可以做到:

Writeln(Outfile,FakeConvert('??'));
Run Code Online (Sandbox Code Playgroud)

并且它会做你期望的(我在发布之前确实尝试过!)

当然,这个问题唯一真正的答案是,因为你一直升级到Delphi XE2:

停止使用已弃用的I/O例程,移至基于TStream

  • 有一个更简单的解决方案.至少在XE2中,`TextFile`和`Writeln()`实际上**支持Unicode.请参阅我的答案以获取示例. (2认同)