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

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

semantics

#1
есть массив или список типа string. Длина строк меняется при работе программы.
Как сохранять этот массив на диск, не фиксируя длину строк?
 

vital

Больной Компом Детектед
29.01.2006
2 432
33
#2
ну просто в файл записать никак?
 

sinkopa

Well-known member
17.06.2009
344
4
#4
есть массив или список типа 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;
 
S

semantics

#5
Спасибо, я именно это хотел узнать - какие есть стандартные средства для работы с динамическими массивами строк.
 
22.02.2015
8
0
#6
Описанный Вами способ мне очень подошел. Но возникла неприятная особенность. При переходе на Delphi XE3 пришлось добавить ansi к string и char. Но сохранить в файл не удалось. В строке
  1. ms.WriteBuffer(Pointer(len),SizeOf(Integer)); ошибка $C0000005.
    Что нужно, чтобы в Delphi XE3 заработало?
 

sinkopa

Well-known member
17.06.2009
344
4
#7
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;
 
22.02.2015
8
0
#8
Спасибо! Я примерно так и доработал Вашу идею:
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.
 

sinkopa

Well-known member
17.06.2009
344
4
#9
Спасибо! Я примерно так и доработал Вашу идею:
Procedure SaveToFile( var strArray: TStringArray);
При посте Delphi-кода пожалуйста пользуйтесь тэгами (в редакторе Кнопочка "Код")

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