将 tList<string> 写入 tFileStream

Writing tList<string> to tFileStream

我在 Windows 10 中使用柏林。我尝试将 tList<string> 保存到文件中。

我知道如何处理 tStringlist、tStreamWriter 和 tStreamReader,但我需要使用 tFileStream,因为应该添加其他类型的数据。

在下面的代码中,读取数据的 Button2Click 循环引发了 eOutOfMemory 异常。当我将简单的字符串值分配给 _String 时,它运行良好,但如果我将 tList 值分配给相同的 _String,则似乎在文件中写入了错误的数据。我无法理解 _String := _List.List[i]_String := 'qwert' 之间的区别。

如何将 tList<string> 写入 tFileSteam?

procedure TForm1.Button1Click(Sender: TObject);
var
  _List: TList<string>;
  _FileStream: TFileStream;
  _String: string;
  i: Integer;
begin
  _List := TList<string>.Create;

  _List.Add('abcde');
  _List.Add('abcde12345');

  _FileStream := TFileStream.Create('test', fmCreate);

  for i := 0 to 1 do
  begin
    _String := _List.List[i]; // _String := 'qwert' works well

    _FileStream.Write(_string, 4);
  end;

  _FileStream.Free;
  _List.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  _FileStream: TFileStream;
  _String: string;
  i: Integer;
begin
  _FileStream := TFileStream.Create('test', fmOpenRead);

  for i := 0 to 1 do
  begin
    _FileStream.Read(_String, 4);

    Memo1.Lines.Add(_String);
  end;

  _FileStream.Free;
end;

如果您在文档中查找 TFileStream.Write 的作用,它会告诉您(继承自 THandleStream.Write):

function Write(const Buffer; Count: Longint): Longint; override;
function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;

Writes Count bytes from the Buffer to the current position in the resource.

现在,Buffer 是未类型化的,因​​此应该是要写入的数据的内存地址。您正在传递一个字符串变量,该变量是对实际字符串数据的引用,该变量的地址包含指向字符串数据的指针。因此,您正在写一个指向该文件的指针。

要更正它,请将字符串的第一个字符传递给缓冲区,....write(_string[1], ... 如果您有编译器指令 {$ZEROBASEDSTRINGS ON},您将使用索引 0。 或者,将字符串类型转换为 PChar 并取消引用它:....write(PChar(_String)^, ...

再看第二个参数,Count。正如文档所说,它表示要写入的 字节 的数量,特别是不是字符。 Delphi 2009 及之后的字符串是 UnicodeString,所以每个字符都是 2 个字节。您需要以字节为单位传递字符串大小。

这将向文件流写入 4 个字符(8 个字节):

_FileStream.Write(_String[1], 4 * SizeOf(Char));

或更好

_FileStream.Write(PChar(_String)^, 4 * SizeOf(Char));

阅读需要做相应的修改,但最值得注意的是,阅读前需要设置字符串长度(长度以字符数计算)。

  SetLength(_String, 4);
  for i := 0 to 1 do
  begin
    _FileStream.Read(_String[1], 4 * SizeOf(Char));

    Memo1.Lines.Add(_String);
  end;

要继续这种低级方法,您可以按如下方式概括字符串写入和读取: 添加一个变量来保存字符串的长度

var
  _String: string;
  _Length: integer;

然后写作

begin
  ...
  for ....
  begin
    _String := _List.List[i];
    _Length := Length(_String);
    _FileStream.Write(_Length, SizeOf(Integer));
    _FileStream.Write(PChar(_List.List[i])^, _Length * SizeOf(Char));
  end;

和阅读

begin
  ...
  for ....
  begin
    _FileStream.Read(_Length, SizeOf(Integer));
    SetLength(_String, _Length);
    _FileStream.Read(_String[1], _Length * SizeOf(Char));
    Memo1.Lines.Add(_String);
  end;

IOW,你先写长度,再写字符串。在阅读时,您先阅读长度,然后阅读字符串。