【问题标题】:How to save classic Delphi string to disk (and read them back)?如何将经典的 Delphi 字符串保存到磁盘(并将它们读回)?
【发布时间】:2016-02-29 21:33:22
【问题描述】:

我想在 Delphi 中完成一项非常基本的任务:将字符串保存到磁盘并重新加载。这似乎微不足道,但自从我升级到 IOUtils 后,我在执行此操作两次时遇到了问题(在那之前还有一次......这就是为什么我做出升级到 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 的内存)
- 应该适用于任何文本文件(显然最大 2GB)
- 没有 IOUtils(太难用了)

【问题讨论】:

  • TStringStream - 不是好主意,因为它不识别源文件编码,所以你可以加载错误的字符序列(默认编码 = Unicode)。并且它不会更改写入的编码,因此如果您将编码设置为 ANSI 以正确读取,则可以得到与另一个问题相同的异常
  • @kami:课程用马。
  • TEncoding.UTF8.GetBytesTEncoding.UTF8.GetString。 FWIW,AppendAllText 如果您的用户不会损坏手头的文件,则可以使用。
  • 但这很重要。如果您需要处理附加到任意文件,那么解决方案将有所不同。无论如何,“我们都爱ANSI”。不,我们都讨厌它。没有单一的 ANSI。只是加载不同的代码页。那是比较没用的。 UTF-8 是我们的最爱。

标签: delphi unicode delphi-xe7


【解决方案1】:

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

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

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

我的建议是做到以下几点:

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

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

【讨论】:

    【解决方案2】:

    只需使用 TStringList,直到文件大小

    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;
    

    【讨论】:

    • 谢谢卡米。那么,缺点在哪里呢? 100MB后代码变慢的原因是什么?
    • 另外,如果我不想给ReadTextFromFile一个Tstringlist作为参数,而是一个字符串,会增加mem消耗?
    • 读取整个文件只是为了能够附加到它是非常浪费的。
    • 无论如何我都会继续投票。小文本文件的好解决方案。
    • @Kenny 字符串作为读取函数的参数 - 你会丢失编码自动检测。
    【解决方案3】:

    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;
    

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

    【讨论】:

    • @Thomas 为什么你认为 INI 文件是相关的?
    • @thomas-这还不是最佳解决方案 :)
    • 公平地说,他多次编辑了这个问题。这是“一个”解决方案,即使还不是最优的。我建议 TTextFile,如果我可以确定它支持 utf,而且他没有提到 TFile 有问题。 (这是 TTextFile 从 iirc 派生的)
    • @Thomas 没有TTextFile。这可能是一个解决方案,但它不是这里提出的问题的解决方案。没有任何编辑提及 INI 文件。你为什么要把 INI 文件带入这件事?
    • 什么是iirc?它与问题有何关系?
    【解决方案4】:

    基于大卫建议的代码:

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

    【讨论】:

    • 如果你认为它所做的只是将字符串写入磁盘,那么代码数量有点荒谬(如果你问我的意见)。
    猜你喜欢
    • 2010-10-11
    • 2010-10-26
    • 1970-01-01
    • 2016-12-01
    • 2014-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多