XE2和Delphi 2009之间的unicode文本文件输出不同?
当我尝试下面的代码时,XE2中的输出似乎与D2009中的不同XE2和Delphi 2009之间的unicode文本文件输出不同?,delphi,unicode,utf-8,Delphi,Unicode,Utf 8,当我尝试下面的代码时,XE2中的输出似乎与D2009中的不同 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,
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编译会在写字板中提供
???
C
txt十六进制代码:EF BB BF 3F 3F 0D 0A B0 43 0D 0A
在Windows XP PC上使用D2009进行编译会在写字板中显示
总结
摄氏度
txt十六进制代码:EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A
我的问题是为什么它不同,以及如何使用旧的文本文件I/O将汉字保存到文本文件中
谢谢 您真的不应该再使用旧的文本I/O了 无论如何,您可以使用十位编码获得UTF-8 TB,如下所示:
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;
function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
N := Length(InStr);
SetLength(Result, N);
Move(InStr[1], Result[1], N);
end;
我不确定是否有更简单的方法将TBytes写入文本文件,也许其他人有更好的主意
编辑:
对于纯二进制文件(
file
而不是TextFile
类型),use可以使用BlockWrite
有几个指示符号,可以告诉您在处理Unicode时什么地方出了问题。在您的例子中,您在生成的输出文件中看到“?
”:当您尝试将某些内容从Unicode转换为代码页时,会出现问号,而目标代码页无法表示请求的字符
查看十六进制转储,很明显(计算行终止符)问号是将两个汉字保存到文件中的结果。这两个字符正好变成了两个问号。这说明Writeln()
决定为您提供帮助,并将文本从UTF8(unicode表示)转换为本地代码页。Delphi团队可能决定这样做,因为旧的I/O例程不应该与UNICODE兼容;因为您正在使用旧的I/O例程编写UTF8字符串,所以它们通过将其转换为代码页来帮助您。你可能不欢迎这一援助之手,但这并不意味着这样做是错误的:这是一个没有记录的领域
既然你现在知道为什么会这样,你就知道该怎么阻止它了。让WriteLn()
知道您正在发送不需要转换的内容。您会发现这并不特别容易,因为Delphi XE2显然“帮助您解决”了您需要的任何问题。例如,像这样的东西不仅会更改字符串类型,还会转换为AnsiString,通过代码页转换例程获得问号:
AnsiString(UTF8String('Whatever Unicode'));
因此,如果您需要单行程序解决方案,您可以尝试转换例程,如下所示:
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;
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:
停止使用不推荐使用的I/O例程,在XE2以后的版本中,移动到基于TStream的,具有一个可选的CodePage
参数,用于设置输出文件的代码页:
function AssignFile(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
Write()
和Writeln()
都有支持UnicodeString
和WideChar
输入的重载
因此,您可以创建一个文件,将其代码页设置为CP\u 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;
procedure TForm1.按钮1点击(发送方:TObject);
变量
输出文件:TFileStream;
BOM:TBytes;
过程写入字节(常量B:T字节);
开始
如果是B'',则为Outfile.WriteBuffer(B[0],长度(B));
结束;
过程WriteStr(常量S:UTF8String);
开始
如果是S“”,则为Outfile.WriteBuffer(S[1],长度);
结束;
过程写入线(常数S:UTF8String);
开始
书面记录;
书面记录(sLineBreak);
结束;
开始
Outfile:=TFileStream.Create('test_chinese.txt',fmCreate);
尝试
写字节(TEncoding.UTF8.GetPreamble);
WriteLine('总结');
写线(°C');
最后
输出文件。自由;
结束;
结束;
旧文本文件IO官方不支持unicode。不要依赖它。如果这样做,请注意实现有缺陷,并且缺陷因Delphi版本而异。这不是TStreamWriter的工作吗?TStreamWriter
?实际上,至少在XE2中,旧样式文件I/O确实对unicode有一些支持。AssigFile()
具有可选的CodePage
参数,Write/ln()
具有接受UnicodeString
和WideChar
输入.TFile.writealBytes的重载(常量路径:字符串;常量字节:TBytes)from System.IOUtils可以将t字节写入一个文件。@Giel不会写入BOM。如果您想将内容以零碎的形式写入,这并不方便。谢谢Jens。我使用旧文本I/O的原因是D2009项目有很多行代码,我只想使用XE2快速而肮脏的解决方案。感谢您Cosmin的帮助这个解决方案和解释也是如此!有一个更简单的解决方案。在XE2中,至少,TextFile
和Writeln()
实际上支持Unicode。请参阅我的答案以获取示例。Delphi XE2(包括最新的XE5)当旧的好writeln被破坏时,它犯了一个很大的错误。writeln非常有用而且速度很快,我的测试用例显示TStreamWriter非常慢。当你编写控制台甚至cgi应用程序时,使用TStreamWriter是不可能的。