当我尝试下面的代码时,与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;
在Windows 8 PC上使用XE2进行编译在WordPad中提供
?? ç
txt十六进制代码:EF BB BF 3F 3F 0D 0A B0 43 0D 0A
在Windows XP PC上使用D2009进行编译,在Wordpad中提供
总结 °C
txt hex code:EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A
我的问题是为什么它有所不同,如何使用旧的文本文件I / O将中文字符保存到文本文件中?
谢谢!
答案 0 :(得分:16)
在XE2之后,AssignFile()
有一个可选的CodePage
参数,用于设置输出文件的代码页:
function AssignFile(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
Write()
和Writeln()
都有重载,支持UnicodeString
和WideChar
输入。
因此,您可以创建一个将其代码页设置为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;
话虽如此,如果你想要在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;
或手动执行文件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;
答案 1 :(得分: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;
我不确定是否有更简单的方法将TBytes写入文本文件,也许其他人有更好的想法。
修改强>
对于纯二进制文件(File
而不是TextFile
类型),可以使用BlockWrite
。
答案 2 :(得分:5)
有几个告诉标志可能会告诉你在处理Unicode时出了什么问题。在你的情况下,你会看到&#34; ?
&#34;在生成的输出文件中:当您尝试将某些内容从Unicode转换为代码页时,您会收到问号,并且目标代码页不能代表请求的字符。
查看十六进制转储,很明显(计算行终止符)问号是将两个中文字符保存到文件的结果。两个字符转换为恰好两个问号。这告诉您Writeln()
决定为您提供帮助并将文本从UTF8(unicode表示)转换为您的本地代码页。 Delphi团队可能决定这样做,因为旧的I / O例程不应该是UNICODE兼容的;因为您使用旧的I / O例程编写UTF8字符串,所以他们会通过将其转换为您的代码页来帮助您。你可能不欢迎这种帮助,但这并不意味着这样做是错误的:它是无证的领域。
既然你现在知道为什么会发生这种情况,你知道如何阻止它。让WriteLn()
知道你发送的内容并不需要转换。您会发现这并不是特别容易,因为德尔福XE2显然可以帮助您解决问题。不管你。例如,像这样的东西并不只是改变字符串类型,它会转换为AnsiString,通过代码页转换例程来获取问号:
AnsiString(UTF8String('Whatever Unicode'));
因此,如果您需要单线解决方案,您可以尝试转换例程,如下所示:
function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
N := Length(InStr);
SetLength(Result, N);
Move(InStr[1], Result[1], N);
end;
然后你就可以做到:
Writeln(Outfile,FakeConvert('总结'));
它会做你期望的事情(我确实在张贴之前尝试过它!)
当然这个问题的唯一正确答案是,因为你一直升级到Delphi XE2: