|
Вопрос # 5 054/ вопрос решён / |
|
Здравствуйте, уважаемые эксперты!
Подскажите пожалуйста, как можно сохранить и прочитать многомерный динамический массив??
ar:array of array of integer;
 |
Вопрос задал: 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™ (статус: Доктор наук), 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™ (статус: Доктор наук), 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™ (статус: Доктор наук), 2 марта 2011, 08:20 [#27]:
Цитата (Егор):
а у тебя случаем Х не глобальная переменная?
Таки да! Вот вся прога целиком (delphi 7).
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
|
|
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 (статус: Посетитель), 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™ (статус: Доктор наук), 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 (статус: Посетитель), 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™ (статус: Доктор наук), 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 (статус: Посетитель), 3 марта 2011, 23:00 [#35]:
>двумерные динмассивы могут быть непрямоугольными
это как,наверное многомерные?
а первый вопрос?
|
|
min@y™ (статус: Доктор наук), 4 марта 2011, 08:17 [#36]:
Цитата (Ham_ele_on):
это как,наверное многомерные?
Я ж тебе показал пример двумерного непрямоугольного массива! Подмассивы там разной длины, смотри внимательнее.
Цитата (Ham_ele_on):
а первый вопрос?
А чо за вопрос?
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
|
|
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™ (статус: Доктор наук), 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 (статус: Посетитель), 4 марта 2011, 12:35 [#40]:
всё, вот теперь понял
то есть по факту получается
Length[dinArray,3],
Length[dinArray[1],2);
Length[dinArray[2],3);
Length[dinArray[3],5);
про иронию я понял, я не понял про какое место.
|
Страницы: [« Предыдущая] [1] [2] [3] [Следующая »]
Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.
|