Запись tList<string> в 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;

1 ответ

Решение

Если вы посмотрите в документах, что TFileStream.Write делает, он говорит вам (наследуется от THandleStream.Write):

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

Записывает количество байтов из буфера в текущую позицию в ресурсе.

Сейчас, 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;

Итак, сначала вы пишете длину, а затем строку. При чтении вы читаете длину, а затем строку.

Другие вопросы по тегам