Управление данными в TMemoryStream

 
0
 
C++
ava
MuadDib | 24.09.2007, 08:31
Здравствуйте, уважаемые.
Возникла потребность работать с большими объемами данных помещенных в TMemotyStream объект. К примеру с большими файлами, загруженными в поток памяти. Необходимо удалять из памяти определенным образом помеченные блоки и соответствующим образом корректировать размер потока памяти.
Для работы с потоком памяти помимо его стандартных средств исползую указатель на его данные:

код условно-описательный, чтобы показать, что делаю:


TMemoryStream *MStream = new TMemoryStream;
char *MainBuf = (char*)MStream->Memory; //указывает на данные в потоке памяти

First(); //на начало
while(!Eof()) //пока не конец блока
{
if(CheckDelRecord()) //проверка на признак необходимости удаления записи
{
MoveMemory((MainBuf + Offset1),(MainBuf + Offset2),MStream->Size - Offset2); //копируем данные со смещения Offset2 на
//Offset1 размером от Offset2 до конца файл
//т.е. "подтягиваем" на место удаленного блока все остльные данные
MStream->SetSize(Offset1 - Offset2);//и уменьшаем размер потока памяти на количество "удаленных" байт
}
else
Next();
}


Данных код нормально работает, с файлами не слишком большого размера. С большими файлами - очень медленно.

Еще вариант, тоже условно-описательный:


First();
int R = 0,
N = 0,
K = 0;

while(K != RecordCount) //RecordCount - количество записей т.е. блоков данных определенного размера в потоке памяти
{
K = N;
FCurrentRecord = N; //указывает на текущую запись

if(CheckDelRecord()) //если запись нужно удалить
{
if(K == RecordCount)
break;

FCurrentRecord = firstNDRec;
while((K++ != RecordCount) && (CheckDelRecord())) //ищем следующую запись которую удалять не нужно
FCurrentRecord = firstNDRec = K ;

if(!CheckDelRecord()) //если нашли
{
FCurrentRecord = R = N; //для рассчета смещений Offset1 и Offset2 описание расчета не привожу для краткости
MoveMemory((MainBuf + Offset1),(MainBuf + Offset2),RecordSize); //копируем данные из "неудаленной" записи в ту область которую "удалить" нужно
FCurrentRecord = K;
DeleteRecord(); //помечаем скопированную запись чтобы удалить ее когда до нее доберемся дабы не делать дубли
}
}
else
R = N;
}

if(R == 0)
Empty();
else
MStream->SetSize(MStream->Size - RecordSize * (RecordCount - R)); //ну и в конце уменьшаем размер потока до необходимого


Данных код тоже работает нормально с файлами не слишком большого размера. С большими файлами медленно, хотя и быстрее чем предыдущий.
Подскажите пожалуйста, как еще можно реализовать данную задачу с наибольшим быстродействием. Интересует именно быстродействие, затраченные ресурсы неважны.
Заранее спасибо!
Ответы (11)
ava
Mihhail | 24.09.2007, 09:17 #
Цитата (MuadDib @  24.9.2007,  11:31 findReferencedText)
//RecordCount - количество записей т.е. блоков данных определенного размера в потоке памяти

Если все записи одинакового размера, то почему бы не перемещать на место удалённой записи самую последнюю запись из "хвоста".

И зачем постоянно менять размер выделенной памяти если:
Цитата (MuadDib @  24.9.2007,  11:31 findReferencedText)
 Интересует именно быстродействие, затраченные ресурсы неважны.

При необходимости можно только увеличивать выделеный блок, при чём с запасом. Ведь известно кол-во записей и выход на удалённые ("мусорные") участки исключён. Если идёт постоянное Del/Add записей это так же увеличит скорость.
ava
MuadDib | 24.09.2007, 09:34 #
Если все записи одинакового размера, то почему бы не перемещать на место удалённой записи самую последнюю запись из "хвоста".

Добавлено позднее:
Дело в том, что данные в потоке должны следовать в том порядке, в котором они были туда загружены, менять последовательность нельзя. Т.е. на место удаленного блока должен вставать следующий непомеченный к удалению блок. Размер выделенной памяти действительно можно изменять не каждый раз, а только единожды после всех операций по перемещению данных, как это сделано во втором примере, однако это не дает ощутимого результата.
ava
Mihhail | 24.09.2007, 11:09 #
Цитата (MuadDib @  24.9.2007,  13:34 findReferencedText)
 Размер выделенной памяти действительно можно изменять не каждый раз, а только единожды после всех операций по перемещению данных

Я имею в виду не изменять размер даже в этом случае. Освобождать память только при завершении программы.

А по алгоритму могу предложить следующее:
Цитата (MuadDib @  24.9.2007,  13:34 findReferencedText)
 на место удаленного блока должен вставать следующий непомеченный к удалению блок

  Время перемещения всех записей всё равно не уменьшить, но можно сделать что бы программа не зависала, т.е. не перемещать все записи за раз. А создавать отдельный поток или просто таймер, который через небольшие промежутки времени смещает блоки небольшой длины(поэксперементировать с их размером). Пока единственное что приходит в голову....

Добавлено через 3 мин
А если забить на это перемещение? Нам же известны номера удалённых записей, при просмотре просто перескакивать через них.
ava
Alexeis | 24.09.2007, 11:50 #
Цитата (MuadDib @  24.9.2007,  07:31 findReferencedText)
Данных код нормально работает, с файлами не слишком большого размера. С большими файлами - очень медленно.


  Перед тем как придумывать определите какие файлы являются большими и каких ресурсов не жалко. Все таки файлы бывают и 100Гб, а в ОЗУ выделить непрерывный блок в 1Гиг порой невозможно. 
ava
Vyacheslav | 24.09.2007, 12:09 #
А зачем в приницпе держать это все в  TMemotyStream  постоянно? А если уж это так нужно, то почему не использовать вектор объектов TMemotyStream?
ava
MuadDib | 24.09.2007, 12:14 #
Если вывести упаковку в отдельный поток на длительное время (т.к. файл размером, например 200 МБ будет паковаться достаточно долго) , получится, что работать с данными будет все равно нельзя, так как они будут постянно изменяться и не будут актуальны. Это получится как работа с реляционной БД в отсутствии транзакций - не знаешь есть ли физически запись в потоке с которой собираешься работать или нет ее и подобные проблемы. Забить тоже нельзя, т.к. смысл упаковки в том, чтобы в конечном итоге уменьшить размер конечного потока и сохранить его в файл. Т.е. ресурсозатраты неважны на этапе работы с потоком, но затем файл, сформированный в итоге должен занять возможный минимум дискового пространства.
Такая вот неприятная штука получается... Нужно и с елки съехать и, желательно, седалище не оцарапать
ava
MuadDib | 24.09.2007, 12:39 #
Если не затруднит, хотелось бы попросить Ермолаева Вячеслава привести пример корректного создания вектора объектов TMemoryStream и работы с ним.
Дело в том, что по-моему вариант: vector <TMemoryStream *> не слишком подходит. Желательно, как мне кажется, хранить не указатели, а сами потоки. И т.к. я не имею большого опыта работы с векторами не соображу как сделать верно... Хотя идея такая тоже приходила.
ava
Alexeis | 24.09.2007, 12:40 #
Предлагаю такой алгоритм. Сначала расчитать где должна быть каждая из записей, затем выделить новый блок нужной длинны и за один проход скопировать каждый блок строго на свое место. скопировать 200Мб в памяти это довольно быстро, должно быть не более 1й секунды.

Добавлено позднее:
Цитата (MuadDib @  24.9.2007,  12:39 findReferencedText)
Дело в том, что по-моему вариант: vector <TMemoryStream *> не слишком подходит.

  TMemoryStream - это клас VCL, потому не может быть статическим. 
ava
MuadDib | 24.09.2007, 12:49 #
Alexeis, я заню, что TMemoryStream не может быть статическим. А предложенный алгоритм начинаю писать, по-моему дельно, нужно пробовать...

ava
Vyacheslav | 24.09.2007, 14:48 #
Цитата (MuadDib @  24.9.2007,  12:39 findReferencedText)
Дело в том, что по-моему вариант: vector <TMemoryStream *> не слишком подходит. Желательно, как мне кажется, хранить не указатели, а сами потоки. И т.к. я не имею большого опыта работы с векторами не соображу как сделать верно... 

Кстати как раз удобнее во всех случаях хранить указатели. Сэкономите на копировании.
Если информацию, удалять-не удалять можно выудить из самого содержимого, то весь Ваш код по сжатию  уложится в  строкe плюс небольшая модификация CheckDelRecord( пишу по памяти без среды, поэтому могут быть ошибки )



vector<TMemoryStream> vec;
bool CheckDelRecord(TMemoryStream* stream) 
{
     bool isDeleted = false;
     //здесь код по проверке нужно ли удалять
     ...
     isDeleted = ...
     if( (isDeleted ) 
     {
          delete stream;
      }
     return isDeleted;
    
}

vec.erase( remove_if(vec.begin(), vec.end(),  CheckDelRecord ), vec.end());


 
ava
MuadDib | 25.09.2007, 13:30 #
Всем спасибо, получилос сделать через вектор быстро и удобно. 
Зарегистрируйтесь или войдите, чтобы написать.
Фирма дня
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Участники
advanced
Отправить