Как организовать запись в файл больших объёмов?

Общие вопросы программирования, алгоритмы и т.п.

Модератор: Модераторы

Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 11:54:06

При обработке большого объёма данных приходится накапливать их в динамическом массиве, а затем переписывать в файл. Для этого использую оттдельный поток, а в нём TFileStream. Сразу же после передачи данных в FileStream я создаю новый динамический массив, куда записываю обрабатываемые данные и по заполнению этого массива вновь таким же образом переписываю их в файл.
Проблема возникает в том, что обработка данных происходит немного быстрее, чем запись их в файл и при попытке открыть очередной TFileStream возникает ошибка: "попытка доступа к занятому файлу". Есть ли какие-то способы решения такой проблемы?
Да и сама идея с периодическим открытием-закрытием потоков мне не совсем нравится. Вот что-нибудь типа большой бочки, куда периодически вылевается по ведру воды, а из неё через краник вода постоянно льётся в умывальник. Если в бочке слишком много воды, то она сообщает - "не вылевай ведро, я переполнена", если вообще нет, то ничего не происходит, просто "в кране нет воды...". На англоязычных сайтах встречал идея о связке TBufStream и TFileStream, но как это сделать - ничего конкретного (на таком же уровне, как выше с бочкой воды).
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 12:44:05

Зачем постоянно закрывать и открывать файл? Почему нельзя всё время держать стрим открытым, и в него писать когда нужно?

Описанная проблема с медленной записью не нова, а описанное решение про большую бочку называется буферизованным выводом. Нужно «завернуть» TFileStream в буферный стрим вызовом TWriteBufStream.Create http://www.freepascal.org/docs-html/fcl ... reate.html указав в Capacity желаемый размер буфера в памяти.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 15:47:50

Дож, спасибо за совет!
А создовать TFileStream и TWriteBufStream надо создавать в отдельном потоке (TThread) или можно в основном?
у меня основной цикл обработки примерно такой:
    for x:=0 to xm do begin
    ...действия...
    for y:=0 to ym do begin
    ...действия...
    for z:=0 to zm do begin
    ...действия...
    if flag then M0[y][z]:=P;
    else M[y][z]:=P;
    end;
    end;
    // Здесь задержка до закрытия открытого Stream, повторное его открытие и передача M0 или М1 (зависит от флага).
    flag:= not flag;
    end;
Где я открываю Stream в этом месте вставить MyTWriteBufStream.Write(M0, Length(M0)); ?
А когда будет заполнен очередной массив M1, как узнать, буфер пуст или ещё занят?
А если использовать MyTWriteBufStream.Write(), то могу ли я отказаться от использования двух массивов, ведь по идее после этой функции можно будет повторно использовать тот же самый массив?
Извините, что столько много "почему" и "как".
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 16:42:13

А создовать TFileStream и TWriteBufStream надо создавать в отдельном потоке (TThread) или можно в основном?

Открытие файла на запись блокирует его открытие на запись из других потоков. Поэтому TFileStream необходимо открыть один раз на все потоки, но предварительно нужно проверить, что этот класс потокобезопасен (нужно читать маны так ли это — я не знаю).

TWriteBufStream, наоборот, лучше создать по одному на каждый поток, если не требуется, чтобы данные из двух различных потоков обазательно были записаны в хронологическом порядке по времени.

Где я открываю Stream в этом месте вставить MyTWriteBufStream.Write(M0, Length(M0)); ?

Вопрос непонятен. MyTWriteBufStream открывается один раз на поток или вообще всю обработку. Write вызывается много раз, если я правильно понимаю. Поэтому напрашивается ответ «конечно же, нет».

А когда будет заполнен очередной массив M1, как узнать, буфер пуст или ещё занят?

Не нужно ничего узнавать про буфер, оно само будет сливаться в файл, когда заполнится.

А если использовать MyTWriteBufStream.Write(), то могу ли я отказаться от использования двух массивов, ведь по идее после этой функции можно будет повторно использовать тот же самый массив?

Да.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 17:53:28

Спасибо, частично разобрался.
Если можно, ещё один вопрос:
Как сделать, чтобы буфер не "съел" всю оперативку?
Делаю вот так:
Код: Выделить всё
uses ... bufstream, ...

  private
   ..........
    Stream: TFileStream; // Поток для создания TIFF-файл и записи в него заговка.
    BufStr: TWriteBufStream;
   .......
   .......
function TForm3.Obrabotka();
var
  ......
  n: Integer;
begin
  if SaveDialog1.Execute then begin // Если выбран открываемый файл.
    try
      Stream:=TFileStream.Create(SaveDialog1.FileName, fmCreate or fmOpenWrite);
      BufStr:=TWriteBufStream.Create(Stream); 
    ........
   Циклы обработки
    n:=BufStr.Position;
    while n>Length(M0)*2 do begin // Ждём освобождение буфера.
      Application.ProcessMessages;
      Sleep(50);
    end;
    BufStr.Write(M0, SizeOf(M0)); // Отправить в буфер.
  end;
end;

n доходит до определённого значения и всё "зависает". В файл информация передана, но "n" не изменяется.
Аналогичная ситуация при закрытии программы. По идее, пока все данные не будут переданы, закрывать программу нельзя.
- Как узнать, сколько данных ещё не передано из буфера, чтобы не сталкнуться с проблемой нехватки оперативной памяти?
- Как узнать, все ли данные записаны в файл или нет, чтобы преждевременно не закрыть приложение?
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 17:58:20

В файл информация передана, но "n" не изменяется.

В какой строке n должна была измениться?

Как узнать, сколько данных ещё не передано из буфера, чтобы не сталкнуться с проблемой нехватки оперативной памяти?

Вопрос непонятен. Оперативка под буфер занимается в момент создания потока (вроде как).
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 18:10:15

Вот такой код у меня:
Код: Выделить всё
...
    while BufStr.Position>Length(M0)*2 do begin // Ждём освобождение буфера.
      n:=BufStr.Position;
      Application.ProcessMessages;
      Sleep(50);
    end;
    BufStr.Write(M0, SizeOf(M0)); // Отправить в буфер.
...

Соврал, "n" изменяется, сначала была 330899, а затем всё время 384. И на этом месте висяк.
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 18:13:48

Почему в этом цикле BufStr.Position обязана стать 0?
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 18:43:57

Потому, что здесь ожидание, когда из буфера всё будет передано, соответственно текущая позиция будет 0 или 1.
А как правильно?
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 18:59:27

А как правильно?

Не ждать ничего вручную — оно само при выполнении Write подождёт, если буфер будет переполнен.
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 19:36:58

Ничего не получается. Посмотрите, пожалуйста, исходник. В чём я не прав.
Вложения
BufStreav_Stream.rar
(140.25 КБ) Скачиваний: 474
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 20:04:03

Я ничего нового не могу сказать — нужно выпилить из кода ручное бесконечное ожидание чего-то непонятного по Position.
Код: Выделить всё
C:\data\downloads\BufStreav_Stream\BufStreav_Stream>diff unit1.pas.orig unit1.pas
69c69
<     while BufStr.Position>Length(M0)*2 do begin // Чтобы успел всё записать в файл.
---
>     {while BufStr.Position>Length(M0)*2 do begin // Чтобы успел всё записать в файл.
72c72
<     end;
---
>     end;}
91c91
<     while BufStr.Position>Length(M0)*2 do begin // Ждём освобождение буфера.
---
>     {while BufStr.Position>Length(M0)*2 do begin // Ждём освобождение буфера.
95c95
<     end;
---
>     end;}
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 20:26:35

Убрал ожидания, сделал отдельнуб кнопку для освобождения памяти и записал он у меня в файл в тысячу раз меньше того, что должен был написать. Ниже результат записи.
Получается, что Lazarus не в состоянии решить такую задачу... или у меня руки кривые.
Если можете, дайте рабочий пример или исправьте ранее приведённый мой код.

Добавлено спустя 18 минут 21 секунду:
Не получается сохранить. На кнопку "Очистить память" нажимал через 10, 30 и 60 сек и получал абсолютно не понятный результат (в файлах My_10sek .bin, My_30sek .bin и My_60sek .bin). Пишет в файл в любом случае только какую-то часть того что должен был. Вообще, какие-то "непонятки" с этим TWriteBufStream...
Прилагаю код и полученные файлы.
Если знаете, как решить проблему, то исправьте код и выложите. Буду очень признателен.
Вложения
Cod.rar
(140.44 КБ) Скачиваний: 427
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

Re: Как организовать запись в файл больших объёмов?

Сообщение Дож » 11.03.2016 20:48:19

ОМГ, да вы травите:
Код: Выделить всё
BufStr.Write(M0, SizeOf(M0));


Это ошибочный и бессмысленный код.

SizeOf(M0) — это примерно 4 в 32-битной архитектуре и примерно 8 в 64-битной архитектуре. Это не функция. Это константа. Она не меняется от размера массива, SizeOf определяется на этапе компиляции.

По адресу переменной M0 (т.е. по адресу @M0 в памяти) хранятся не данные. В ней хранится указатель на массив. А так как у Вас бинарный массив, в переменной в итоге хранится указатель на массив указателей на массивы байтов. Передавать M0 как нетипизированный var-буфер бессмысленно почти всегда.

Правильный код записи бинарного массива в поток будет такой:
Код: Выделить всё
    for y := 0 to High(M0) do
      BufStr.Write(M0[y][0], Length(M0[y])*SizeOf(M0[y][0])); // Записать данные из массива номер y в файл
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Как организовать запись в файл больших объёмов?

Сообщение shyub » 11.03.2016 21:01:27

Дож!
Не знаю, как вас благодорить. Заработало!
Оказалось "Дело не в бабине - расп...здяй сидел в кабине"
shyub
постоялец
 
Сообщения: 112
Зарегистрирован: 25.11.2014 23:15:19

След.

Вернуться в Общее

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 12

Рейтинг@Mail.ru