Страница 1 из 2

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

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

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

СообщениеДобавлено: 11.03.2016 12:44:05
Дож
Зачем постоянно закрывать и открывать файл? Почему нельзя всё время держать стрим открытым, и в него писать когда нужно?

Описанная проблема с медленной записью не нова, а описанное решение про большую бочку называется буферизованным выводом. Нужно «завернуть» TFileStream в буферный стрим вызовом TWriteBufStream.Create http://www.freepascal.org/docs-html/fcl ... reate.html указав в Capacity желаемый размер буфера в памяти.

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

СообщениеДобавлено: 11.03.2016 15:47:50
shyub
Дож, спасибо за совет!
А создовать 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(), то могу ли я отказаться от использования двух массивов, ведь по идее после этой функции можно будет повторно использовать тот же самый массив?
Извините, что столько много "почему" и "как".

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

СообщениеДобавлено: 11.03.2016 16:42:13
Дож
А создовать TFileStream и TWriteBufStream надо создавать в отдельном потоке (TThread) или можно в основном?

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

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

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

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

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

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

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

Да.

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

СообщениеДобавлено: 11.03.2016 17:53:28
shyub
Спасибо, частично разобрался.
Если можно, ещё один вопрос:
Как сделать, чтобы буфер не "съел" всю оперативку?
Делаю вот так:
Код: Выделить всё
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" не изменяется.
Аналогичная ситуация при закрытии программы. По идее, пока все данные не будут переданы, закрывать программу нельзя.
- Как узнать, сколько данных ещё не передано из буфера, чтобы не сталкнуться с проблемой нехватки оперативной памяти?
- Как узнать, все ли данные записаны в файл или нет, чтобы преждевременно не закрыть приложение?

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

СообщениеДобавлено: 11.03.2016 17:58:20
Дож
В файл информация передана, но "n" не изменяется.

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

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

Вопрос непонятен. Оперативка под буфер занимается в момент создания потока (вроде как).

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

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

Соврал, "n" изменяется, сначала была 330899, а затем всё время 384. И на этом месте висяк.

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

СообщениеДобавлено: 11.03.2016 18:13:48
Дож
Почему в этом цикле BufStr.Position обязана стать 0?

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

СообщениеДобавлено: 11.03.2016 18:43:57
shyub
Потому, что здесь ожидание, когда из буфера всё будет передано, соответственно текущая позиция будет 0 или 1.
А как правильно?

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

СообщениеДобавлено: 11.03.2016 18:59:27
Дож
А как правильно?

Не ждать ничего вручную — оно само при выполнении Write подождёт, если буфер будет переполнен.

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

СообщениеДобавлено: 11.03.2016 19:36:58
shyub
Ничего не получается. Посмотрите, пожалуйста, исходник. В чём я не прав.

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;}

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

СообщениеДобавлено: 11.03.2016 20:26:35
shyub
Убрал ожидания, сделал отдельнуб кнопку для освобождения памяти и записал он у меня в файл в тысячу раз меньше того, что должен был написать. Ниже результат записи.
Получается, что Lazarus не в состоянии решить такую задачу... или у меня руки кривые.
Если можете, дайте рабочий пример или исправьте ранее приведённый мой код.

Добавлено спустя 18 минут 21 секунду:
Не получается сохранить. На кнопку "Очистить память" нажимал через 10, 30 и 60 сек и получал абсолютно не понятный результат (в файлах My_10sek .bin, My_30sek .bin и My_60sek .bin). Пишет в файл в любом случае только какую-то часть того что должен был. Вообще, какие-то "непонятки" с этим TWriteBufStream...
Прилагаю код и полученные файлы.
Если знаете, как решить проблему, то исправьте код и выложите. Буду очень признателен.

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 в файл

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

СообщениеДобавлено: 11.03.2016 21:01:27
shyub
Дож!
Не знаю, как вас благодорить. Заработало!
Оказалось "Дело не в бабине - расп...здяй сидел в кабине"