如何用缓冲流版本的 ReadLn 替换 Readln?

How to replace Readln with Buffered stream version of ReadLn?

我从 xml 个文件中读取 UTF8 内容,然后需要保存并按需重新加载。我正在从 AssignFile/Writeln/Readln 转换为 David Heffernan 的缓冲流:Buffered files (for faster disk access)

我有简单的新 WriteLn 和 ReadLn 程序,WriteLn 可以工作,但我不能让 ReadLn 工作。

我对 ReadLn 的概念是处理:

  1. 读取缓冲区
  2. 寻找换行符
  3. 从 PrevPos 获取文本到 CurrPos-1
  4. 保存缓冲区的其余部分以添加到下一个读取缓冲区的第一行

新的 WriteLn 程序:

{ * New WriteLn * }
procedure TForm1.Button2Click(Sender: TObject);
var FileOut: TWriteCachedFileStream;
  vText: string;
  vUTF8Text: RawByteString;
begin
  FileOut := TWriteCachedFileStream.Create('c:\tmp\file.txt');
  try
    vText := 'Delphi';
    vUTF8Text := Utf8Encode(vText + sLineBreak);
    FileOut.WriteBuffer(PAnsichar(vUTF8Text)^, Length(vUTF8Text));
    vText := 'VB源码';
    vUTF8Text := Utf8Encode(vText + sLineBreak);
    FileOut.WriteBuffer(PAnsichar(vUTF8Text)^, Length(vUTF8Text));
    vText := 'Java源码';
    vUTF8Text := Utf8Encode(vText + sLineBreak);
    FileOut.WriteBuffer(PAnsichar(vUTF8Text)^, Length(vUTF8Text));
  finally
    FileOut.Free;
  end;
end;

但是读取有问题,因为它无法从文件读取缓冲。 TReadOnlyCachedFileStream 的 Read 函数发生错误:

错误说:

"Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x004069ca: write of address 0x00000010'."


function TReadOnlyCachedFileStream.Read(var Buffer; Count: Longint): Longint;
begin
  ...
  Move(CachePtr^, BufferPtr^, NumOfBytesToCopy); { <- Error occurs here }
  ...
end;

这是我的 ReadLn 程序 - 无法正常工作,因为我无法解决错误:

{ * New ReadLn * }
procedure TForm1.Button3Click(Sender: TObject);
var FileIn: TReadOnlyCachedFileStream;
  vLinesCounter, vCurrPos, vPrevPos: integer;
  vBuffer: TBytes;
  vUTF8Text, vPrevUTF8Text: string;
  vFilesize,vBytesRead,vNumberOfBytes: Int64;
  vCh: Char;
begin
  vLinesCounter := 0;
  FileIn := TReadOnlyCachedFileStream.Create('c:\tmp\file.txt');
  try
    vFilesize := FileIn.Size;
    while FileIn.Position < vFilesize do
    begin
      vBytesRead:=FileIn.Read(vBuffer, 65536);
      vNumberOfBytes := vNumberOfBytes + vBytesRead;
      {1. Find Line break
       2. Get Text from PrevPos to CurrPos-1
       3. Save rest of buffer to add to first line of next Read buffer}
      vCurrPos := 0; vPrevPos := 0;
      while vCurrPos < vBytesRead do
      begin
        vCh:=Chr(vBuffer[vCurrPos]);
        if (vCh = #13) Or (vCh = #10) then { is New line }
        begin
          if vPrevUTF8Text <> '' then
            vUTF8Text := vPrevUTF8Text + TEncoding.UTF8.GetString(vBuffer, vPrevPos, vCurrPos - 1) { Add previous text that was not separet line}
          else
            vUTF8Text := TEncoding.UTF8.GetString(vBuffer, vPrevPos, vCurrPos - 1);
          vPrevPos := vCurrPos; { Save Pos for next line }
          Inc(vLinesCounter);
          Memo1.Lines.Add(vUTF8Text);
        end;
      end;
      { save rest of text as start of next line }
      if vCurrPos < Length(vBuffer) then
        vPrevUTF8Text := TEncoding.UTF8.GetString(vBuffer, vPrevPos, vCurrPos - 1);
    end;
  finally
    FileIn.Free
  end;
  Memo1.Lines.Add('Lines read: '+IntToStr(vLinesCounter));
end;

RTL 在 System.Classes 单元中有自己的 TStreamReader and TStreamWriter classes,你应该让他们为你做繁重的工作,例如:

procedure TForm1.Button2Click(Sender: TObject);
var
  FileOut: TStreamWriter;
begin
  FileOut := TStreamWriter.Create('c:\tmp\file.txt', False, TEncoding.UTF8);
  try
    FileOut.WriteLine('Delphi');
    FileOut.WriteLine('VB源码');
    FileOut.WriteLine('Java源码');
  finally
    FileOut.Free;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  FileIn: TStreamReader;
  vLinesCounter: Integer;
begin
  vLinesCounter := 0;
  FileIn := TStreamReader.Create('c:\tmp\file.txt', True);
  try
    while not FileIn.EndOfStream do
    begin
      Memo1.Lines.Add(FileIn.ReadLine);
      Inc(vLinesCounter);
    end;
  finally
    FileIn.Free;
  end;
  Memo1.Lines.Add('Lines read: '+IntToStr(vLinesCounter));
end;

如果你想使用 David 的缓冲区 classes(注意 Delphi10.1 Berlin 增加了一个新的 TBufferedFileStream class),你仍然可以这样做,例如:

procedure TForm1.Button2Click(Sender: TObject);
var
  FileStrm: TWriteCachedFileStream;
  FileOut: TStreamWriter;
begin
  FileStrm := TWriteCachedFileStream.Create('c:\tmp\file.txt');
  try
    FileOut := TStreamWriter.Create(FileStrm, TEncoding.UTF8);
    try
      FileOut.WriteLine('Delphi');
      FileOut.WriteLine('VB源码');
      FileOut.WriteLine('Java源码');
    finally
      FileOut.Free;
    end;
  finally
    FileStrm.Free;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  FileStrm: TReadOnlyCachedFileStream;
  FileIn: TStreamReader;
  vLinesCounter: Integer;
begin
  vLinesCounter := 0;
  FileStrm := TReadOnlyCachedFileStream.Create('c:\tmp\file.txt');
  try
    FileIn := TStreamReader.Create(FileStrm, True);
    try
      while not FileIn.EndOfStream do
      begin
        Memo1.Lines.Add(FileIn.ReadLine);
        Inc(vLinesCounter);
      end;
    finally
      FileIn.Free;
    end;
  finally
    FileStrm.Free;
  end;
  Memo1.Lines.Add('Lines read: '+IntToStr(vLinesCounter));
end;