сохранение массива строк на диск

  • Автор темы Автор темы semantics
  • Дата начала Дата начала
S

semantics

есть массив или список типа string. Длина строк меняется при работе программы.
Как сохранять этот массив на диск, не фиксируя длину строк?
 
есть массив или список типа string. Длина строк меняется при работе программы.
Как сохранять этот массив на диск, не фиксируя длину строк?
Безусловно, самый "незаморочистый" способ сохранения в файл динамического массива строк, это использование промежуточного Стринг листа (TStringList)
Код:
//...

type
// Объявляем тип (Delphi "не любит" работать с <array of string> как параметр внутри процедуры )
TStringArray = array of string;
TOptArrayCommand = (opSaveToFile, opLoadFromFile); // Опции управления функцией

//...

var
SArray: TStringArray;

implementation

function ArrayOperation1(const FileName: string; var strArray: TStringArray; Opt: TOptArrayCommand):Integer;
var
SList: TStringList;
i: Integer;
begin
Result := 0;
case Opt of
opSaveToFile  : begin // Запись в файл

if (Length(strArray) < 1) then Exit;

SList := TStringList.Create;

for i := Low(strArray) to High(strArray) do
SList.Add(strArray[i]);

SList.SaveToFile(FileName);
Result := SList.Count;

SList.Free;
end;

opLoadFromFile : begin  // Чтение из файла
// обнулили массив
SetLength(strArray,0);

if not FileExists(FileName) then Exit;

SList := TStringList.Create;
SList.LoadFromFile(FileName);

SetLength(strArray,SList.Count);

for i := Low(strArray) to High(strArray) do
strArray[i] := SList[i];

Result := SList.Count;
SList.Free;
end;
end;
end;
Но это НЕПРАВИЛЬНЫЙ способ :)
Если вдруг окажется, что строчки (элементы массива) содержат в себе символы разрыва строки (перевода каретки) то?...
Правильно, прочитанный из файла массив, будет содержать совсем не то количество элементов которое мы сохраняли...
Сам массив станет несколько длиннее, а строчки (его элементы) несколько короче... :KillMe:
хотя... не исключаю случаев когда такое поведение приведенного выше метода может быть как раз полезным...

Тем не менее, если вы хотите прочитать из файла данные в том же виде (состоянии) в каком сохраняли, предлагаю способ, который считаю более ПРАВИЛЬНЫМ
Код:
//...

type
// Объявляем тип (Delphi "не любит" работать с <array of string> как параметр внутри процедуры )
TStringArray = array of string;
TOptArrayCommand = (opSaveToFile, opLoadFromFile); // Опции управления функцией

//...

var
SArray: TStringArray;

implementation

function ArrayOperation2(const FileName: string; var strArray: TStringArray; Opt: TOptArrayCommand):Integer;
const
RES_ID = 'MYSTRARRAY';
var
ms: TMemoryStream;
i,len: Integer;
s: string;
begin
Result := 0;
case Opt of
opSaveToFile  : begin	// Запись в файл

len := Length(strArray);
if (len < 1) then Exit;

ms := TMemoryStream.Create;
ms.Seek(0,soFromBeginning);

// записали имя ресурса
// чтобы потом (при чтении) быть уверенным что это наш массив
ms.WriteBuffer(RES_ID,Length(RES_ID)*SizeOf(Char));

// записали общую длину массива
ms.WriteBuffer(Pointer(len),SizeOf(Integer));

Result := len;

for i := Low(strArray) to High(strArray) do
begin
len := Length(strArray[i]);
// записали длину i-той строчки
ms.WriteBuffer(Pointer(len),SizeOf(Integer));
// записали i-тую строчку
if (len > 0) then
ms.WriteBuffer(strArray[i][1],len*SizeOf(Char));
end;
ms.Seek(0,soFromBeginning);
try
ms.SaveToFile(FileName);
except
Result := 0; // а вдруг не удалось записать файл?
end;
ms.Free;
end;

opLoadFromFile : begin // Чтение из файла
// обнулили массив
SetLength(strArray,0);

if not FileExists(FileName) then Exit;
ms := TMemoryStream.Create;
ms.LoadFromFile(FileName);
ms.Seek(0,soFromBeginning);

// прочитали имя ресурса
SetLength(s,Length(RES_ID));
ms.ReadBuffer(s[1],Length(RES_ID)*SizeOf(Char));

// Убедились что это именно то что мы хотим
// прочитать в массив строк
if (s <> RES_ID) then
begin
ms.Free;
Exit;
end;

// прочитали общую длину массива
ms.ReadBuffer(Pointer(len),SizeOf(Integer));

// выставили длину массива
SetLength(strArray,len);

for i := Low(strArray) to High(strArray) do
begin
// прочитали длину i-той строчки
ms.ReadBuffer(Pointer(len),SizeOf(Integer));
if (len > 0) then
begin
// выставили длину i-того элемента массива
SetLength(strArray[i],len);
// прочитали i-тую строчку
ms.ReadBuffer(strArray[i][1],len*SizeOf(Char));
end
else
strArray[i] := '';
end;

Result := Length(strArray);
ms.Free;
end;
end;
end;

Пример использования для обоих методов

Код:
procedure TForm1.Button1Click(Sender: TObject);
var
ResultCount: Integer;
begin
ResultCount := ArrayOperation1('c:\arraydata1.txt',SArray,opSaveToFile);
ShowMessage(IntToStr(ResultCount));

ResultCount := ArrayOperation1('c:\arraydata1.txt',SArray,opLoadFromFile);
ShowMessage(IntToStr(ResultCount));

if (ResultCount > 0) then
ShowMessage(SArray[0]);

ResultCount := ArrayOperation2('c:\arraydata2.txt',SArray,opSaveToFile);
ShowMessage(IntToStr(ResultCount));

ResultCount := ArrayOperation2('c:\arraydata2.txt',SArray,opLoadFromFile);
ShowMessage(IntToStr(ResultCount));

if (ResultCount > 0) then
ShowMessage(SArray[0]);
end;
 
Спасибо, я именно это хотел узнать - какие есть стандартные средства для работы с динамическими массивами строк.
 
Описанный Вами способ мне очень подошел. Но возникла неприятная особенность. При переходе на Delphi XE3 пришлось добавить ansi к string и char. Но сохранить в файл не удалось. В строке
  1. ms.WriteBuffer(Pointer(len),SizeOf(Integer)); ошибка $C0000005.
    Что нужно, чтобы в Delphi XE3 заработало?
 
ms.WriteBuffer(Pointer(len),SizeOf(Integer)); ошибка $C0000005.
Тип Pointer в XE3 имеет разную размерность для Win32 и Win64.
Надо типизированный указатель использовать (или можно просто сам var в качестве буффера подставить)
Вот так будет работать правильно и без ошибок:
Код:
  // ...
type
  // Объявляем тип (Delphi "не любит" работать с <array of string> как параметр внутри процедуры )
  TStringArray = array of AnsiString;
  TOptArrayCommand = (opSaveToFile, opLoadFromFile); // Опции управления функцией
 
// ...
 
function ArrayOperation2(const FileName: AnsiString; var strArray: TStringArray;
  Opt: TOptArrayCommand): Integer;
const
  RES_ID: PAnsiChar = 'MYSTRARRAY';
var
  ms: TMemoryStream;
  i, len,n: Integer;
  s: AnsiString;
begin
  Result := 0;
  n := StrLen(RES_ID);
  case Opt of
	opSaveToFile:
	  begin // Запись в файл
		len := Length(strArray);
		if (len < 1) then
		  Exit;
		ms := TMemoryStream.Create;
		ms.Seek(0, soFromBeginning);
		// записали имя ресурса
		// чтобы потом (при чтении) быть уверенным что это наш массив
		ms.WriteBuffer(RES_ID^, n { SizeOf(RES_ID)});
		// записали общую длину массива
		ms.WriteBuffer(len, SizeOf(Integer));
		Result := len;
		for i := Low(strArray) to High(strArray) do
		begin
		  len := Length(strArray[i]);
		  // записали длину i-той строчки
		  ms.WriteBuffer(len, SizeOf(Integer));
		  // записали i-тую строчку
		  if (len > 0) then
			ms.WriteBuffer(PAnsiChar(strArray[i])^, len);
		end;
		ms.Seek(0, soFromBeginning);
		try
		  ms.SaveToFile(FileName);
		except
		  Result := 0; // а вдруг не удалось записать файл?
		end;
		ms.Free;
	  end;
	opLoadFromFile:
	  begin // Чтение из файла
		// обнулили массив
		SetLength(strArray, 0);
		if not FileExists(FileName) then
		  Exit;
		ms := TMemoryStream.Create;
		ms.LoadFromFile(FileName);
		ms.Seek(0, soFromBeginning);
		// прочитали имя ресурса
		SetLength(s, n);
		ms.ReadBuffer(s[1], n);
		// Убедились что это именно то что мы хотим
		// прочитать в массив строк
		if (s <> RES_ID) then
		begin
		  ms.Free;
		  Exit;
		end;
 
		// прочитали общую длину массива
		ms.ReadBuffer(len, SizeOf(Integer));
 
		// выставили длину массива
		SetLength(strArray, len);
 
		for i := Low(strArray) to High(strArray) do
		begin
		  // прочитали длину i-той строчки
		  ms.ReadBuffer(len, SizeOf(Integer));
		  if (len > 0) then
		  begin
			// выставили длину i-того элемента массива
			SetLength(strArray[i], len);
			// прочитали i-тую строчку
			ms.ReadBuffer(strArray[i][1], len);
		  end
		  else
			strArray[i] := '';
		end;
		Result := Length(strArray);
		ms.Free;
	  end;
  end;
 
end;
 
Спасибо! Я примерно так и доработал Вашу идею:
Procedure SaveToFile( var strArray: TStringArray);
const
RES_ID = 'MAIN_FILE';
var
ms: TMemoryStream;
i,len: Integer;
s: ansistring;
Result:integer;
begin
Result := 0;
len := Length(strArray);
if (len < 1) then Exit;
ms := TMemoryStream.Create;
ms.Seek(0,soFromBeginning);
// записали имя ресурса
// чтобы потом (при чтении) быть уверенным что это наш массив
len := Length(RES_ID);
// ms.WriteBuffer(len,SizeOf(Integer));
ms.WriteBuffer(RES_ID[1],Len*SizeOf(ansiChar));
// записали общую длину массива
len := Length(strArray);
ms.WriteBuffer(len,SizeOf(Integer));
Result := len;
for i := Low(strArray) to High(strArray) do
begin
len := Length(strArray);
// записали длину i-той строчки
ms.WriteBuffer(len,SizeOf(Integer));
// записали i-тую строчку
if (len > 0) then
ms.WriteBuffer(strArray[1],len*SizeOf(ansiChar));
end;
ms.Seek(0,soFromBeginning);
try
ms.SaveToFile(frmMain.FName);
except
Result := 0; // а вдруг не удалось записать файл?
end;
ms.Free;
end;
Кстати Win 7 32 bit.
 
Спасибо! Я примерно так и доработал Вашу идею:
Procedure SaveToFile( var strArray: TStringArray);
При посте Delphi-кода пожалуйста пользуйтесь тэгами (в редакторе Кнопочка "Код")

Имелась в виду не сама Ваша операционная система, а целевая платформа указанная в свойствах проекта, в опциях компилятора
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!

Курс AD