如何将经典 Delphi 字符串保存到磁盘(并读回)?
How to save classic Delphi string to disk (and read them back)?
我想在 Delphi 中完成一项非常非常基本的任务:将字符串保存到磁盘并将其加载回来。这看起来微不足道,但自从我升级到 IOUtils 之后,我在执行此操作时遇到了两次问题(之前还有一次……这就是为什么我做出 'brilliant' 决定升级到 IOUtils 的原因)。
我使用这样的东西:
procedure WriteToFile(CONST FileName: string; CONST uString: string; CONST WriteOp: WriteOperation);
begin
if WriteOp= (woOverwrite)
then IOUtils.TFile.WriteAllText (FileName, uString) //overwrite
else IOUtils.TFile.AppendAllText(FileName, uString); //append
end;
简单吧?会出什么问题?好吧,我最近遇到了 IOUtils 中的一个(另一个)错误。所以,TFile 是 . The bug is detailed .
任何人都可以分享一个不基于 IOUtils 且已知 有效的替代方案(或只是您的 thoughts/ideas)?嗯...上面的代码对我也有用过一段时间...所以,我知道很难保证一段代码(无论多小)是否真的有用!
此外,我真的很想让我的 WriteToFile 程序在可能的情况下将字符串保存到 ANSI 文件(uString 仅包含 ANSI 字符),否则保存为 Unicode。
然后 ReadAFile 函数应该自动检测编码并正确读回字符串。
这个想法是仍然有文本编辑器会错误地 open/interpret 一个 Unicode/UTF 文件。因此,只要有可能,就给用户一个好的旧 ANSI 文本文件。
所以:
- Overwrite/Append
- 尽可能保存为 ANSI
- 内存效率高(当要加载的文件为 2GB 时不要占用 4GB 内存)
- 应该适用于任何文本文件(显然最大 2GB)
- 没有 IOUtils(错误太多无法使用)
Inifiles 单元应该支持 unicode。至少根据这个答案:How do I read a UTF8 encoded INI file?
ini 文件常用于存储字符串、整数、布尔值甚至字符串列表。
procedure TConfig.ReadValues();
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
try
FMainScreen_Top := appINI.ReadInteger('Options', 'MainScreen_Top', -1);
FMainScreen_Left := appINI.ReadInteger('Options', 'MainScreen_Left', -1);
FUserName := appINI.ReadString('Login', 'UserName', '');
FDevMode := appINI.ReadBool('Globals', 'DevMode', False);
finally
appINI.Free;
end;
end;
procedure TConfig.WriteValues(OnlyWriteAnalyzer: Boolean);
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
try
appINI.WriteInteger('Options', 'MainScreen_Top', FMainScreen_Top);
appINI.WriteInteger('Options', 'MainScreen_Left', FMainScreen_Left);
appINI.WriteString('Login', 'UserName', FUserName);
appINI.WriteBool('Globals', 'DevMode', FDevMode);
finally
appINI.Free;
end;
end;
另请参阅关于 ini 文件的 embarcadero 文档:http://docwiki.embarcadero.com/Libraries/Seattle/en/System.IniFiles.TIniFile
只需使用 TStringList,直到文件大小 < ~50-100Mb(这取决于 CPU 速度):
procedure ReadTextFromFile(const AFileName: string; SL: TStringList);
begin
SL.Clear;
SL.DefaultEncoding:=TEncoding.ANSI; // we know, that old files has this encoding
SL.LoadFromFile(AFileName, nil); // let TStringList detect real encoding.
// if not - it just use DefaultEncoding.
end;
procedure WriteTextToFile(const AFileName: string; const TextToWrite: string);
var
SL: TStringList;
begin
SL:=TStringList.Create;
try
ReadTextFromFile(AFileName, SL); // read all file with encoding detection
SL.Add(TextToWrite);
SL.SaveToFile(AFileName, TEncoding.UTF8); // write file with new encoding.
// DO NOT SET SL.WriteBOM to False!!!
finally
SL.Free;
end;
end;
Then the ReadAFile function should automagically detect the encoding and correctly read the string back.
这是不可能的。如果解释为任何文本编码,则存在格式正确的文件。例如参见 [=13=].
这意味着你的目标无法实现,你需要改变它们。
我的建议是执行以下操作:
- 选择单一编码,UTF-8,并坚持使用。
- 如果文件不存在,创建它并写入UTF-8 字节。
- 如果文件存在,打开它,查找到末尾,并追加UTF-8 字节。
不支持 UTF-8 的文本编辑器不值得支持。如果您愿意,可以在创建文件时包括一个 UTF-8 BOM。使用 TEncoding.UTF8.GetBytes
和 TEncoding.UTF8.GetString
进行编码和解码。
代码基于 David 的建议:
{--------------------------------------------------------------------------------------------------
READ/WRITE UNICODE
--------------------------------------------------------------------------------------------------}
procedure WriteToFile(CONST FileName: string; CONST aString: String; CONST WriteOp: WriteOperation= woOverwrite; WritePreamble: Boolean= FALSE); { Write Unicode strings to a UTF8 file. It can also write a preamble }
VAR
Stream: TFileStream;
Preamble: TBytes;
sUTF8: RawByteString;
aMode: Integer;
begin
ForceDirectories(ExtractFilePath(FileName));
if (WriteOp= woAppend) AND FileExists(FileName)
then aMode := fmOpenReadWrite
else aMode := fmCreate;
Stream := TFileStream.Create(filename, aMode, fmShareDenyWrite); { Allow read during our writes }
TRY
sUTF8 := Utf8Encode(aString); { UTF16 to UTF8 encoding conversion. It will convert UnicodeString to WideString }
if (aMode = fmCreate) AND WritePreamble then
begin
preamble := TEncoding.UTF8.GetPreamble;
Stream.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end;
if aMode = fmOpenReadWrite
then Stream.Position:= Stream.Size; { Go to the end }
Stream.WriteBuffer( PAnsiChar(sUTF8)^, Length(sUTF8) );
FINALLY
FreeAndNil(Stream);
END;
end;
procedure WriteToFile (CONST FileName: string; CONST aString: AnsiString; CONST WriteOp: WriteOperation);
begin
WriteToFile(FileName, String(aString), WriteOp, FALSE);
end;
function ReadFile(CONST FileName: string): String; {Tries to autodetermine the file type (ANSI, UTF8, UTF16, etc). Works with UNC paths }
begin
Result:= System.IOUtils.TFile.ReadAllText(FileName);
end;
我想在 Delphi 中完成一项非常非常基本的任务:将字符串保存到磁盘并将其加载回来。这看起来微不足道,但自从我升级到 IOUtils 之后,我在执行此操作时遇到了两次问题(之前还有一次……这就是为什么我做出 'brilliant' 决定升级到 IOUtils 的原因)。
我使用这样的东西:
procedure WriteToFile(CONST FileName: string; CONST uString: string; CONST WriteOp: WriteOperation);
begin
if WriteOp= (woOverwrite)
then IOUtils.TFile.WriteAllText (FileName, uString) //overwrite
else IOUtils.TFile.AppendAllText(FileName, uString); //append
end;
简单吧?会出什么问题?好吧,我最近遇到了 IOUtils 中的一个(另一个)错误。所以,TFile 是
任何人都可以分享一个不基于 IOUtils 且已知 有效的替代方案(或只是您的 thoughts/ideas)?嗯...上面的代码对我也有用过一段时间...所以,我知道很难保证一段代码(无论多小)是否真的有用!
此外,我真的很想让我的 WriteToFile 程序在可能的情况下将字符串保存到 ANSI 文件(uString 仅包含 ANSI 字符),否则保存为 Unicode。
然后 ReadAFile 函数应该自动检测编码并正确读回字符串。
这个想法是仍然有文本编辑器会错误地 open/interpret 一个 Unicode/UTF 文件。因此,只要有可能,就给用户一个好的旧 ANSI 文本文件。
所以:
- Overwrite/Append
- 尽可能保存为 ANSI
- 内存效率高(当要加载的文件为 2GB 时不要占用 4GB 内存)
- 应该适用于任何文本文件(显然最大 2GB)
- 没有 IOUtils(错误太多无法使用)
Inifiles 单元应该支持 unicode。至少根据这个答案:How do I read a UTF8 encoded INI file?
ini 文件常用于存储字符串、整数、布尔值甚至字符串列表。
procedure TConfig.ReadValues();
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
try
FMainScreen_Top := appINI.ReadInteger('Options', 'MainScreen_Top', -1);
FMainScreen_Left := appINI.ReadInteger('Options', 'MainScreen_Left', -1);
FUserName := appINI.ReadString('Login', 'UserName', '');
FDevMode := appINI.ReadBool('Globals', 'DevMode', False);
finally
appINI.Free;
end;
end;
procedure TConfig.WriteValues(OnlyWriteAnalyzer: Boolean);
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
try
appINI.WriteInteger('Options', 'MainScreen_Top', FMainScreen_Top);
appINI.WriteInteger('Options', 'MainScreen_Left', FMainScreen_Left);
appINI.WriteString('Login', 'UserName', FUserName);
appINI.WriteBool('Globals', 'DevMode', FDevMode);
finally
appINI.Free;
end;
end;
另请参阅关于 ini 文件的 embarcadero 文档:http://docwiki.embarcadero.com/Libraries/Seattle/en/System.IniFiles.TIniFile
只需使用 TStringList,直到文件大小 < ~50-100Mb(这取决于 CPU 速度):
procedure ReadTextFromFile(const AFileName: string; SL: TStringList);
begin
SL.Clear;
SL.DefaultEncoding:=TEncoding.ANSI; // we know, that old files has this encoding
SL.LoadFromFile(AFileName, nil); // let TStringList detect real encoding.
// if not - it just use DefaultEncoding.
end;
procedure WriteTextToFile(const AFileName: string; const TextToWrite: string);
var
SL: TStringList;
begin
SL:=TStringList.Create;
try
ReadTextFromFile(AFileName, SL); // read all file with encoding detection
SL.Add(TextToWrite);
SL.SaveToFile(AFileName, TEncoding.UTF8); // write file with new encoding.
// DO NOT SET SL.WriteBOM to False!!!
finally
SL.Free;
end;
end;
Then the ReadAFile function should automagically detect the encoding and correctly read the string back.
这是不可能的。如果解释为任何文本编码,则存在格式正确的文件。例如参见 [=13=].
这意味着你的目标无法实现,你需要改变它们。
我的建议是执行以下操作:
- 选择单一编码,UTF-8,并坚持使用。
- 如果文件不存在,创建它并写入UTF-8 字节。
- 如果文件存在,打开它,查找到末尾,并追加UTF-8 字节。
不支持 UTF-8 的文本编辑器不值得支持。如果您愿意,可以在创建文件时包括一个 UTF-8 BOM。使用 TEncoding.UTF8.GetBytes
和 TEncoding.UTF8.GetString
进行编码和解码。
代码基于 David 的建议:
{--------------------------------------------------------------------------------------------------
READ/WRITE UNICODE
--------------------------------------------------------------------------------------------------}
procedure WriteToFile(CONST FileName: string; CONST aString: String; CONST WriteOp: WriteOperation= woOverwrite; WritePreamble: Boolean= FALSE); { Write Unicode strings to a UTF8 file. It can also write a preamble }
VAR
Stream: TFileStream;
Preamble: TBytes;
sUTF8: RawByteString;
aMode: Integer;
begin
ForceDirectories(ExtractFilePath(FileName));
if (WriteOp= woAppend) AND FileExists(FileName)
then aMode := fmOpenReadWrite
else aMode := fmCreate;
Stream := TFileStream.Create(filename, aMode, fmShareDenyWrite); { Allow read during our writes }
TRY
sUTF8 := Utf8Encode(aString); { UTF16 to UTF8 encoding conversion. It will convert UnicodeString to WideString }
if (aMode = fmCreate) AND WritePreamble then
begin
preamble := TEncoding.UTF8.GetPreamble;
Stream.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end;
if aMode = fmOpenReadWrite
then Stream.Position:= Stream.Size; { Go to the end }
Stream.WriteBuffer( PAnsiChar(sUTF8)^, Length(sUTF8) );
FINALLY
FreeAndNil(Stream);
END;
end;
procedure WriteToFile (CONST FileName: string; CONST aString: AnsiString; CONST WriteOp: WriteOperation);
begin
WriteToFile(FileName, String(aString), WriteOp, FALSE);
end;
function ReadFile(CONST FileName: string): String; {Tries to autodetermine the file type (ANSI, UTF8, UTF16, etc). Works with UNC paths }
begin
Result:= System.IOUtils.TFile.ReadAllText(FileName);
end;