如何将经典的Delphi字符串保存到磁盘(并将其读回)?

时间:2016-02-29 21:33:22

标签: delphi unicode delphi-xe7

我想在Delphi中实现一个非常基本的任务:将字符串保存到磁盘并加载回来。这似乎是微不足道的,但是自从我升级到IOUtils之后我做了这个TWICE就遇到了问题(之前又一次......这就是为什么我把'辉煌'的决定升级到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是buggy。该错误详细here

任何人都可以分享不是基于IOUtils和已知的替代方案(或仅仅是您的想法/想法)吗?嗯......上面的代码也为我工作了一段时间......所以,我知道如果难以保证一段代码(无论多小)都能真正起作用!

此外,我真的希望让我的WriteToFile过程在可能的情况下将字符串保存到ANSI文件(uString仅包含ANSI字符),否则为Unicode。 然后ReadAFile函数应自动检测编码并正确读回字符串 我们的想法是,仍有文本编辑器会错误地打开/解释Unicode / UTF文件。因此,只要有可能,请向用户提供一个好的旧ANSI文本文件。

所以:
  - 覆盖/附加
  - 尽可能保存为ANSI   - 内存效率(当加载文件为2GB时,不要吃4GB的ram)
  - 应该使用任何文本文件(显然最多2GB)
  - 没有IOUtils(太麻烦无法使用)

4 个答案:

答案 0 :(得分:5)

  

然后ReadAFile函数应该自动检测编码并正确读回字符串。

这是不可能的。如果解释为任何文本编码,则存在格式良好的文件。例如,请参阅The Notepad file encoding problem, redux

这意味着您的目标无法实现,您需要更改它们。

我的建议是做以下事情:

  • 选择一个单独的编码,UTF-8,并坚持下去。
  • 如果该文件不存在,请创建该文件并向其写入UTF-8字节。
  • 如果文件存在,请将其打开,搜索到最后,并附加UTF-8字节。

不了解UTF-8的文本编辑器不值得支持。如果您有意愿,请在创建文件时包含UTF-8 BOM。使用TEncoding.UTF8.GetBytesTEncoding.UTF8.GetString进行编码和解码。

答案 1 :(得分:4)

只需使用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;

答案 2 :(得分:1)

Inifiles单元应该支持unicode。至少根据这个答案:How do I read a UTF8 encoded INI file?

Inifiles常用于存储字符串,整数,布尔值甚至字符串列表。

    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;

另请参阅关于inifiles的embarcadero文档:http://docwiki.embarcadero.com/Libraries/Seattle/en/System.IniFiles.TIniFile

答案 3 :(得分:1)

基于大卫建议的代码:

{--------------------------------------------------------------------------------------------------
 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;