当非 unicode 页面是韩文 (949) 时,为什么 ReadLn 会错误解释 UTF8 文本?

Why does ReadLn mis-interpret UTF8 text when non-unicode page is Korean (949)?

在 Delphi XE2 中,当系统区域设置为英语时,我只能使用 AssignFileReadLn() 例程读取和显示 unicode 字符(来自 UTF8 编码文件)。

失败的地方
如果我将非 unicode 应用程序的系统区域设置为韩语(我认为是代码页 949)并重复相同的读取,我的一些 UTF8 多字节对将被替换为 F。这仅适用于使用 ReadLn 而不是使用 TFile.ReadAllText(aFilename, TEncoding.UTF8)TFileStream.Read().

测试
1. 我创建了一个文本文件,UTF8 w/o BOM (Notepad++),其中包含以下字符(第二行显示的十六进制等价物):

테스트
ed 85 8c ec 8a a4 ed 8a b8
  1. 用TMemo控件写一个Delphi XE 2 Windows表单应用程序:

    procedure TForm1.ReadFile(aFilename:string);
    var
      gFile     : TextFile;
      gLine     : RawByteString;
      gWideLine : string;
    begin
      AssignFile(gFile, aFilename);
      try
        Reset(gFile);
        Memo1.Clear;
        while not EOF(gFile) do
        begin
          ReadLn(gFile, gLine);
          gWideLine := UTF8ToWideString(gLine);
          Memo1.Lines.Add(gWideLine);
        end;
      finally
        CloseFile(gFile);
      end;
    end;
    
  2. 我在执行 UTF8ToWideString 对话之前检查了 gLine 的内容,在英语/美国语言环境下 Windows 它是:

    $ED C $EC A $A4 $ED A $B8

顺便说一句,如果我读取带有 BOM 的同一个文件,我会得到正确的 3 字节前导码,并且执行 UTF8 解码时的输出是相同的。目前一切正常!

  1. 切换 Windows 7 (x64) 以使用韩语作为不支持 Unicode 的应用程序的代码页(区域和语言 --> 管理选项卡 --> 更改系统区域设置 --> 韩语(韩国).重启电脑。

  2. 使用上述应用程序读取相同的文件(UTF8 w/o BOM)并且 gLine 现在具有十六进制值:

    F C $EC A $A4 F F

    TMemo 中的输出:?????

  3. 假设 ReadLn()(和 Read() 正试图将 UTF8 序列映射为韩语多字节序列(即试图解释 $ED $85,不能所以潜艇在问号 $3F).

  4. 使用TFileStream准确读取预期的字节数(9 w/o BOM),内存中的十六进制现在正好是:

    $ED C $EC A $A4 $ED A $B8

    TMemo 中的输出:테스트(完美!)

问题:懒惰 - 我有很多遗留例程逐行解析可能很大的文件,我想确保我不需要编写例程手动读取每个文件的新行。

问题:

  1. 为什么 Read() 没有返回我在文件中找到的确切字节字符串?是不是因为我使用的是 TextFile 类型,所以 Delphi 正在使用非 unicode 代码页进行一定程度的解释?

  2. 是否有逐行读取 UTF8 编码文件的内置方法?

更新:

刚刚看到 Rob Kennedy 对 this post 的解决方案,它让我重新认识了 TStreamReader,它逐行回答了关于优雅地读取 UTF8 文件的问题。

Is there a built in way to read a UTF8 encoded file line by line?

使用TStreamReader. It has a ReadLine()方法。

    procedure TForm1.ReadFile(aFilename:string);
    var
      gFile     : TStreamReader;
      gLine     : string;
    begin
      Memo1.Clear;
      gFile := TStreamReader.Create(aFilename, TEncoding.UTF8, True);
      try
        while not gFile.EndOfStream do
        begin
          gLine := gFile.ReadLine;
          Memo1.Lines.Add(gLine);
        end;
      finally
        gFile.Free;
      end;
    end;

话虽如此,这个特定示例可以大大简化:

    procedure TForm1.ReadFile(aFilename:string);
    begin
      Memo1.Lines.LoadFromFile(aFilename, TEncoding.UTF8);
    end;