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

person JO SeongGng    schedule 27.08.2016    source источник
comment
Вы пробовали сериализовать его?   -  person Ignacio Vazquez-Abrams    schedule 27.08.2016
comment
@ IgnacioVazquez-Abrams Нет, я этого не делал. Мне жаль, что я никогда об этом не слышал.   -  person JO SeongGng    schedule 27.08.2016
comment
Почему вы не используете TStringList, у него есть методы SaveToFile и LoadFromFile?   -  person whosrdaddy    schedule 27.08.2016
comment
TList<string> - вполне разумный выбор. Менее загроможден этот TStringList свойством Objects[], если вы его не используете.   -  person David Heffernan    schedule 27.08.2016
comment
@whosrdaddy Мне очень жаль, что я неправильно ввел tStringlist как tStringgrid. Я поправил. Я знаю, как использовать tStringlist, но мне нужно добавить целое, расширенное или что-то еще в тот же tFilestream. Спасибо.   -  person JO SeongGng    schedule 27.08.2016
comment
Если вы просто выбросите все это в файл, как вы узнаете, какого типа данные при чтении. Подумай немного об этом. Спрашивать, как это кодировать, бессмысленно. Вы даже не знаете, что хотите делать. Кодирование будет самой простой частью. Один из вариантов - написать вывод в формате json. Но решать только вам.   -  person David Heffernan    schedule 27.08.2016
comment
@DavidHeffernan Я знаю, какие типы данных и какого размера они существуют, потому что я их пишу и читаю в порядке и каждом размере в соответствии с записанной последовательностью. Кстати это проблема сериализации? Я не могу найти полезный контент по ссылкам. Спасибо.   -  person JO SeongGng    schedule 27.08.2016
comment
Взгляните на этот пример из документации Delphi: docwiki.embarcadero.com/ CodeExamples / Сиэтл / ru /. Речь идет о TStreamWriter и TStreamReader. Они могут писать и читать довольно много типов, включая строки. Это сериализация.   -  person Rudy Velthuis    schedule 27.08.2016
comment
Я очень сомневаюсь, что ваш нынешний подход принесет много пользы.   -  person David Heffernan    schedule 28.08.2016


Ответы (1)


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

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

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

Теперь 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, вы сначала пишете длину, а затем строку. При чтении вы читаете длину, а затем строку.

person Tom Brunberg    schedule 27.08.2016
comment
Написание _List.Add ('abcde'); _String: = _List.List [0]; _FileStream.Write (_string [1], 5); и чтение SetLength (_String, 5); _FileStream.Read (_String [1], 5); работает хорошо. Ваш комментарий был настолько полезным, что я могу решить проблему. Запись и чтение должны иметь размер каждого элемента tList, и это раздражает. Лучше использовать ShortString статической строки. Большое спасибо. - person JO SeongGng; 28.08.2016
comment
В случае строки [1] длина должна быть length * 2. Ваш комментарий по поводу Unicode был очень полезным. Большое спасибо. - person JO SeongGng; 22.09.2016