Экспертная система Delphi.int.ru

Сообщество программистов
Общение, помощь, обмен опытом

Логин:
Пароль:
Регистрация | Забыли пароль?

Delphi.int.ru Expert

Другие разделы портала

Переход к вопросу:

#   

Статистика за сегодня:  


Лучшие эксперты

Подробнее »



Вопрос # 5 054

/ вопрос решён /

Здравствуйте, уважаемые эксперты!
Подскажите пожалуйста, как можно сохранить и прочитать многомерный динамический массив??

ar:array of array of integer;

Ham_ele_on Вопрос решён, но можно продолжить его обсуждение в мини-форуме

Вопрос задал: Ham_ele_on (статус: Посетитель)
Вопрос отправлен: 27 февраля 2011, 18:37
Состояние вопроса: решён, ответов: 1.

Ответ #1. Отвечает эксперт: min@y™

Если массив является матрицей (т.е. он прямоугольный), то вот тебе 2 функции.

type
  TMatrix = array of array of Integer;
 
// Сохранение матрицы X в файл FileName
function SaveMatrixToFile(const X: TMatrix; const FileName: string): Boolean;
var
  Stream: TFileStream;
  RowCount, ColCount, xSize: Cardinal;
begin
  RowCount:= Length(X); // Кол-во строк
  ColCount:= Length(X[0]); // Кол-во стролбцов
  xSize:= RowCount * ColCount * SizeOf(Integer); // Размер матрицы в байтах
 
  try
    Stream:= TFileStream.Create(FileName, fmCreate or fmShareExclusive);
    try
      Stream.Write(RowCount, SizeOf(Cardinal)); // Запись кол-ва строк
      Stream.Write(ColCount, SizeOf(Cardinal)); // Запись кол-ва столбцов
      Stream.Write(X[0, 0], xSize);             // Запись матрицы
    finally
      Stream.Free();
    end;
 
    Result:= True;
  except
    Result:= False;
  end;
end;
 
// Загрузка матрицы X из файла FileName
function LoadMatrixFromFile(var X: TMatrix; const FileName: string): Boolean;
var
  Stream: TFileStream;
  RowCount, ColCount, xSize: Cardinal;
begin
  try
    Stream:= TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
    try
      Stream.Read(RowCount, SizeOf(Cardinal)); // Чтение кол-ва строк
      Stream.Read(ColCount, SizeOf(Cardinal)); // Чтение кол-ва столбцов
      xSize:= RowCount * ColCount * SizeOf(Integer); // Размер матрицы в байтах
      SetLength(X, RowCount, ColCount);              // Выделение памяти
      Stream.Read(X[0, 0], xSize);             // Чтение матрицы
    finally
      Stream.Free();
    end;
 
    Result:= True;
  except
    Result:= False;
  end;
end;
Программа, в которой я отлаживал эти функции, торчит здесь.

Ответ отправил: min@y™ (статус: Доктор наук)
Время отправки: 28 февраля 2011, 08:19
Оценка за ответ: 5


Мини-форум вопроса

Всего сообщений: 42; последнее сообщение — 4 марта 2011, 21:55; участников в обсуждении: 4.

Страницы: [« Предыдущая] [1] [2] [3] [Следующая »]

min@y™

min@y™ (статус: Доктор наук), 1 марта 2011, 19:37 [#21]:

Да, действительно, я был неправ.
Однако, я 1хрен не понимаю, почему сохранение и загрузка отрабатывают правильно? Паскалевский менеджер памяти жжот или что-то другое? Никогда у меня не возникало таких вопросов. Давай вместе найдём решение?!
  Source matrix:
 
   0   3   0   2   3   2
   0   8   2   8   9   0
   4   2   6   0   7   5
   8   5   6   2   2   3
   5   8   4   3   3   3
 
  Saving to file D:\Temp\Matrix.bin - SUCCESS.
 
  Loading form file D:\Temp\Matrix.bin - SUCCESS.
 
  Loaded matrix:
 
   0   3   0   2   3   2
   0   8   2   8   9   0
   4   2   6   0   7   5
   8   5   6   2   2   3
   5   8   4   3   3   3
 
  Matrix in memory:
 
  [0, 0] =  0: 0x00D00A24
  [0, 1] =  3: 0x00D00A28
  [0, 2] =  0: 0x00D00A2C
  [0, 3] =  2: 0x00D00A30
  [0, 4] =  3: 0x00D00A34
  [0, 5] =  2: 0x00D00A38
  [1, 0] =  0: 0x00D00A48
  [1, 1] =  8: 0x00D00A4C
  [1, 2] =  2: 0x00D00A50
  [1, 3] =  8: 0x00D00A54
  [1, 4] =  9: 0x00D00A58
  [1, 5] =  0: 0x00D00A5C
  [2, 0] =  4: 0x00D00A6C
  [2, 1] =  2: 0x00D00A70
  [2, 2] =  6: 0x00D00A74
  [2, 3] =  0: 0x00D00A78
  [2, 4] =  7: 0x00D00A7C
  [2, 5] =  5: 0x00D00A80
  [3, 0] =  8: 0x00D00A90
  [3, 1] =  5: 0x00D00A94
  [3, 2] =  6: 0x00D00A98
  [3, 3] =  2: 0x00D00A9C
  [3, 4] =  2: 0x00D00AA0
  [3, 5] =  3: 0x00D00AA4
  [4, 0] =  5: 0x00D00AB4
  [4, 1] =  8: 0x00D00AB8
  [4, 2] =  4: 0x00D00ABC
  [4, 3] =  3: 0x00D00AC0
  [4, 4] =  3: 0x00D00AC4
  [4, 5] =  3: 0x00D00AC8
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
min@y™

min@y™ (статус: Доктор наук), 1 марта 2011, 19:47 [#22]:

  [0, 5] =  2: 0x00D00A38
  [1, 0] =  0: 0x00D00A48
Разрыв в 16 байт можно как-то объяснить? 4 байта - данные, 4 байта - адрес следующего подмассива. А остальные 8 байт - это что?
И почему сохранение с помощью моей функции проходит успешно, как и загрузка посредством второй моей функции?

КТО-НИБУДЬ МОЖЕТ ЭТО ОБЪЯСНИТЬ??!!11
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Егор

Егор (статус: 10-ый класс), 1 марта 2011, 21:08 [#23]:

проверил в d7 - пишет в файл так, как и предсказывал Вадим - с мусором между строками. соответственно, считывается не вся матрица

Цитата (min@y™):

Разрыв в 16 байт можно как-то объяснить? 4 байта - данные, 4 байта - адрес следующего подмассива. А остальные 8 байт - это что?

нет, не так. скорее всего, SetLength выделяет память блоками, кратными по размеру степени 2. где-то читал, что при таком поведении фрагментация памяти будет наименьшей. и в этой выделенной памяти указатели не хранятся.

Цитата (min@y™):

И почему сохранение с помощью моей функции проходит успешно, как и загрузка посредством второй моей функции?

а вот это - хз.
у меня - не работает.

записал:
 41 42 43 44 45
 46 47 48 49 4A
 4B 4C 4D 4E 4F
прочитал:
 41 42 43 44 45
 46 47 48 49 4A
 00 00 00 00 00
Опасайтесь багов в приведенном выше коде; я только доказал корректность, но не запускал его.
— Donald E. Knuth.
Егор

Егор (статус: 10-ый класс), 1 марта 2011, 21:45 [#24]:

не, не в двойке дело. между строками всегда 12 байт:
...
  [0, 3] = 68: 0x00D54C44
  [1, 0] = 69: 0x00D54C54
...
...
  [0, 9] = 74: 0x00D54C50
  [1, 0] = 75: 0x00D54C60
...
пробовал менять настройки проекта, размеры массива - одинаково.
d7. в других дельфях не проверял.
Опасайтесь багов в приведенном выше коде; я только доказал корректность, но не запускал его.
— Donald E. Knuth.
Егор

Егор (статус: 10-ый класс), 1 марта 2011, 22:07 [#25]:

Цитата (min@y™):

Разрыв в 16 байт можно как-то объяснить? 4 байта - данные, 4 байта - адрес следующего подмассива. А остальные 8 байт - это что?

судя по дампу памяти, получается, что в 16 байт распределяются следующим образом:
4 байта (0-3) - последнее число строки (т.е. данные)
4 байта (3-7) - так и не понял что, но не мусор; число зависит от размера массива
4 байта (8-11) - единичка (количество подмассивов?)
4 байта (12-15) - количество элементов (для length)
Опасайтесь багов в приведенном выше коде; я только доказал корректность, но не запускал его.
— Donald E. Knuth.
Егор

Егор (статус: 10-ый класс), 2 марта 2011, 04:53 [#26]:

Цитата (min@y™):

Однако, я 1хрен не понимаю, почему сохранение и загрузка отрабатывают правильно?

а у тебя случаем Х не глобальная переменная?
Опасайтесь багов в приведенном выше коде; я только доказал корректность, но не запускал его.
— Donald E. Knuth.
min@y™

min@y™ (статус: Доктор наук), 2 марта 2011, 08:20 [#27]:

Цитата (Егор):

а у тебя случаем Х не глобальная переменная?

Таки да! Вот вся прога целиком (delphi 7).
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
min@y™

min@y™ (статус: Доктор наук), 2 марта 2011, 09:02 [#28]:

Ну, вроде разобрались. Итак, по отрицательному смещению каждого подмассива есть 16 байт служебной инфы, которую в файл писать не надо и читать оттуда - тоже. С учётом этого я приношу свои извинения аудитории и представляю исправленные функции сохранения и загрузки:
// Сохранение матрицы X в файл FileName
function SaveMatrixToFile(const X: TMatrix; const FileName: string): Boolean;
var
  Stream: TFileStream;
  RowCount, ColCount, Row: Integer;
begin
  RowCount:= Length(X); // Кол-во строк
 
  try
    Stream:= TFileStream.Create(FileName, fmCreate or fmShareExclusive);
    try
      Stream.Write(RowCount, SizeOf(Integer)); // Запись кол-ва строк
 
      // Сохранение каждой строки отдельно
      for Row:= 0 to RowCount - 1 do
        begin
          ColCount:= Length(X[Row]); // Кол-во элементов строки Row
          Stream.Write(ColCount, SizeOf(Integer)); // Запись кол-ва элементов строки Row
          Stream.Write(X[Row, 0], ColCount * SizeOf(Integer)); // Запись строки Row
        end;
    finally
      Stream.Free();
    end;
 
    Result:= True;
  except
    Result:= False;
  end;
end;
 
// Загрузка матрицы X из файла FileName
function LoadMatrixFromFile(var X: TMatrix; const FileName: string): Boolean;
var
  Stream: TFileStream;
  RowCount, ColCount, Row: Integer;
begin
  try
    Stream:= TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
    try
      Stream.Read(RowCount, SizeOf(Integer)); // Чтение кол-ва строк
      SetLength(X, RowCount); // Выделение памяти под указатели
 
      for Row:= 0 to RowCount - 1 do
        begin
          Stream.Read(ColCount, SizeOf(Integer)); // Чтение кол-ва элементов строки Row
          SetLength(X[Row], ColCount);            // Выделение памяти под элементы строки Row
          Stream.Read(X[Row, 0], ColCount * SizeOf(Integer)); // Чтение строки Row
        end;
    finally
      Stream.Free();
    end;
 
    Result:= True;
  except
    Result:= False;
  end;
end;

З.Ы. Теперь с помощью этих функций можно работать с двумерными массивами непрямоугольной формы. Программа для тестирования - тут.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Вадим К

Вадим К (статус: Академик), 2 марта 2011, 11:55 [#29]:

Теперь я расскажу о том, как массивы хранятся в памяти:)
итак. объявив переменную x типа array of array of что-то, получается следующее.
Сама переменная x имеет размер 4 байта, является по сути своей указателем и сохраняется в стеке (если она объявленна локальной для процедуры/функции) или в специальной памяти, если глобальной.
когда мы пишем Setlength(x,3,3) происходит следующая магия.
где то в куче выделяется 8 байт + 4 * 3 и сам указатель x начинает указывать в середину этой выделенной памяти, на 9 по счету байт.
8 байт слева - это 4 байта счетчика ссылок и 4 байта размер. Счетчик ссылок нужен, что бы экономить на копировании массивов (например, если его передаем по значению в другую функцию, но там не изменяем - передается только указатель и счетчик ссылок увеличивается на 1) и правильно освобождать память.
функция length на самом деле функцией не является - компилятор подставляет вместо нее просто пару инструкций ассемблера, что бы извлечь число по адресу (указатель x - 4).
Под FreePascal у меня получалось, что адрес хранится на единичку меньше, чем реальный
А вот следующие 3 группы по 4 байта - это указатели на подмассивы, которые устроенны аналогично. Поэтому между массивами и есть пропуски. 8 байт с 12 теперь имеют смысл:)

Теперь сделаем немного магии:)
Вызов SetLength(x,3,3); стал доступен в последних делфи. В 4 делфи его точно не было и приходилось писать так
  SetLength(x,3);
  for i := 0 to 2 do
    SetLength(x[i],3);

Но кто мешает сделать так
  SetLength(x,3);
  for i := 2 downto 0 do
    SetLength(x[i],3);
В здравом уме так конечно никто не будет инициализировать, но в сложном коде может и не такое быть - к примеру размерности могут меняться. Если теперь запустить тот код, который я приводил в одном с своих предыдущих постов, то можно увидеть вообще странное.
[0x0] 0xB7868048, 0
[0x1] 0xB7868049, 1
[0x2] 0xB786804A, 2
[1x0] 0xB7868038, -16
[1x1] 0xB7868039, -15
[1x2] 0xB786803A, -14
[2x0] 0xB7868028, -32
[2x1] 0xB7868029, -31
[2x2] 0xB786802A, -30
теперь элементы перепутались:) Но нет ничего дивного. Компилятор получив инстукцию SetLength ищет первый доступный участок памяти подходящего размера (это очень принципиальная поправка!, ведь этот участок может быть в любом месте, у нас то лабораторный пример!) и размещает там следующий подмассив.
Сделаем следующий фокус. Теперь у нас будет два массива одинакового размера. И вот так инициализируем
    SetLength(x,3);
    SetLength(z,3);
    for i := 0 to 2 do begin
        SetLength(x[i],3);
        SetLength(z[i],3);
    end;
И что же мы видим?
[0x0] 0xB788A028, 0
[0x1] 0xB788A029, 1
[0x2] 0xB788A02A, 2
[1x0] 0xB788A048, 32
[1x1] 0xB788A049, 33
[1x2] 0xB788A04A, 34
[2x0] 0xB788A068, 64
[2x1] 0xB788A069, 65
[2x2] 0xB788A06A, 66
Видим, что "дыры" стали больше. И это правильно - внутри их разместился другой массив.


Осталось только выяснить судьбу тех 4 байтов в "дырках". Мне кажется, что их происхождение следующее.
Как известно, в 3 делфи появились string в современном их понимании. До этого string были ограничены в 256 байт. А динамические массивы появились с 4 делфи. Если заглянуть внутрь, то эти две сущности устроенны одинаково. Только для строк счетчик ссылок может быть равен -1 - это значит строка-константа. Для массивов такого нет (в делфи не может быть константного динамического массива, так как для константного массива нельзя применять операцию изменения размера (он же константный!), и нужно инициализировать значениями при объявлении, но это будет уже так называемый "открытый массив").
У строк всегда в самый конец добавляется ещё один невидимый элемент, который равен 0 - для того, что бы легко можно было эти строки использовать как Си-строки. Для массивов это не имеет смысла, но переделывать готовый код никто не хотел. Вот и появились в хвостах по лишнему элементу.
В своих примерах я объявлял элемент массива как один байт, но почему же у меня 4 байта? А это уже так называемое выравнивание. Процессор быстрее оперирует данными, если они выровнены по границе в 4 байта. А некоторые инструкции процессора могут работать только с выровненными данными.
А также параллельно получаем очень хороший побочный эффект. Известно, что программисты часто забывают, что динамические массивы начинаются с нуля и до (размер-1) и пишут цикл от 1 до размер. И в большинстве случаев именно из-за описанного выше поведения ничего плохого не будет. Более того, код даже будет нормально работать и падать только в некоторых, "загадочных случаях".

Для любознательных рекомендую почитать эту статью - http://docs.embarcadero.com/products/rad_studio/radstudio2007/RS2007_helpupdates/HUpdate4/EN/html/devcommon/internaldataformats_xml.html и даже распечатать себе.
Галочка "подтверждения прочтения" - вселенское зло.
Ham_ele_on

Ham_ele_on (статус: Посетитель), 2 марта 2011, 21:48 [#30]:

пытаюсь найти отличия(кроме читаемости)
 try
  b:=Length(ar);
 fs:=TFileStream.Create('c:\Temp.txt',fmCreate);
   fs.Write(b,SizeOf(b));
 for a := 0 to High(ar) do
  fs.Write(ar[a][0],SizeOf(integer)*Length(ar[a]));
  finally
    fs.Free;
  end;

try
  fs:=TFileStream.Create('c:\Temp.txt',fmOpenRead);
  fs.Read(b,SizeOf(b));
    SetLength(ar,b,3);
 for a := 0 to High(ar) do
  fs.Read(ar[a][0],SizeOf(integer)*Length(ar[a]));
 
finally
  fs.Free;
end;
min@y™

min@y™ (статус: Доктор наук), 2 марта 2011, 21:59 [#31]:

Ты ещё побайтно сравни, ога!
Создание объекта внутри try-finally не имеет смысла, не делай так. И обзывай переменные так, чтобы имя несло информацию о переменной.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Вадим К

Вадим К (статус: Академик), 3 марта 2011, 15:09 [#32]:

имеется ввиду, что правильно писать так
b:=Length(ar);
fs:=TFileStream.Create('c:\Temp.txt',fmCreate);
try
  fs.Write(b,SizeOf(b));
  for a := 0 to High(ar) do
    fs.Write(ar[a][0],SizeOf(integer)*Length(ar[a]));
finally
  fs.Free;
end;
Если объект не удалось создать, что удалять его не нужно. Ведь в таком случае fs будет содержать мусор и это лишний повод сделать ещё одно исключение.
Галочка "подтверждения прочтения" - вселенское зло.
Ham_ele_on

Ham_ele_on (статус: Посетитель), 3 марта 2011, 20:11 [#33]:

Вадим К , спасибо за пояснения.как то и не подумал об этом.
>Ты ещё побайтно сравни, ога!
это я так понмаю, про fs.Write(b,SizeOf(b));?
то есть эта и fs.Write(b,SizeOf(integer)); по сути одно и тоже 4-байта?

и ещё, зачем так сложно
 try
      Stream.Read(RowCount, SizeOf(Integer)); // Чтение кол-ва строк
      SetLength(X, RowCount); // Выделение памяти под указатели
 
      for Row:= 0 to RowCount - 1 do
        begin
          Stream.Read(ColCount, SizeOf(Integer)); // Чтение кол-ва элементов строки Row
          SetLength(X[Row], ColCount);            // Выделение памяти под элементы строки Row
          Stream.Read(X[Row, 0], ColCount * SizeOf(Integer)); // Чтение строки Row
        end;

если можно
 fs:=TFileStream.Create('c:\Temp.txt',fmOpenRead);
try
  fs.Read(arRow,SizeOf(word));
  fs.Read(arCol,SizeOf(word));
  SetLength(ar,arRow,arCol); //сразу указать размер массива
 for Row := 0 to High(ar) do
  fs.Read(ar[Row,0],SizeOf(word)*Length(ar[Row]));
  finally
  fs.Free;
min@y™

min@y™ (статус: Доктор наук), 3 марта 2011, 20:55 [#34]:

Цитата (Ham_ele_on):

сразу указать размер массива

Да потому что двумерные динмассивы могут быть непрямоугольными (НЕматрицами), и мои функции всё равно будут с ними работать правильно. Пример массива:
1 2 3 4 5 6 7
1 2 3
1 2 3 5 4
1 2
1 2 3 4 5 6
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Ham_ele_on

Ham_ele_on (статус: Посетитель), 3 марта 2011, 23:00 [#35]:

>двумерные динмассивы могут быть непрямоугольными
это как,наверное многомерные?
а первый вопрос?
min@y™

min@y™ (статус: Доктор наук), 4 марта 2011, 08:17 [#36]:

Цитата (Ham_ele_on):

это как,наверное многомерные?

Я ж тебе показал пример двумерного непрямоугольного массива! Подмассивы там разной длины, смотри внимательнее.

Цитата (Ham_ele_on):

а первый вопрос?

А чо за вопрос?
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Ham_ele_on

Ham_ele_on (статус: Посетитель), 4 марта 2011, 08:46 [#37]:

первый вопрос
>Ты ещё побайтно сравни, ога!
это я так понмаю, про fs.Write(b,SizeOf(b));?
то есть эта и fs.Write(b,SizeOf(integer)); по сути одно и тоже 4-байта?

ты рушишь мои представления о массивах
Array[n];одномерный, Array[n,m]; двумерный(многомерный), array[n,m,x];????
min@y™

min@y™ (статус: Доктор наук), 4 марта 2011, 08:57 [#38]:

Цитата (Ham_ele_on):

первый вопрос >Ты ещё побайтно сравни, ога! это я так понмаю, про fs.Write(b,SizeOf(b));? то есть эта и fs.Write(b,SizeOf(integer)); по сути одно и тоже 4-байта?

Ты там сравнивал 2 куска кода. А я сыронизировал, предложив сравнить их побайтно. :)

Цитата (Ham_ele_on):

ты рушишь мои представления о массивах Array[n];одномерный, Array[n,m]; двумерный(многомерный), array[n,m,x];????

array[n,m,x] - трёхмерный, не спорю.
Но двумерные динамические массивы могут быть непрямоугольными, трёхмерные могут быть непараллелепипедными и т.д.
Двумерный дин.массив - это одномерный массив одномерных массивов, каждый из которых может быть любой длины. Смотри пример выше.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Вадим К

Вадим К (статус: Академик), 4 марта 2011, 10:44 [#39]:

А ещё есть понятие "треугольный массив":) Это когда так
1
2 3
4 5 6
7 8 9 10
Или наоборот:) И есть целая серия задач, где такие массивы могут быть применены.
Галочка "подтверждения прочтения" - вселенское зло.
Ham_ele_on

Ham_ele_on (статус: Посетитель), 4 марта 2011, 12:35 [#40]:

всё, вот теперь понял
то есть по факту получается
Length[dinArray,3],
Length[dinArray[1],2);
Length[dinArray[2],3);
Length[dinArray[3],5);

про иронию я понял, я не понял про какое место.

Страницы: [« Предыдущая] [1] [2] [3] [Следующая »]

Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.

Версия движка: 2.6+ (26.01.2011)
Текущее время: 25 апреля 2026, 10:17
Выполнено за 0.07 сек.