:: MVP ::
|
|
:: RSS ::
|
|
|
Для начала определимся с методом решения. Можно воспользоваться процедурами
BlockRead и BlockWrite, а можно использовать поток TFileStream. С моей
(субъективной) точки зрения, процедуры вроде BlockRead и BlockWrite морально
устарели. По этому я, не колеблясь, выбрал второй вариант.
Рассмотрим основные свойства и методы класса TFileStream, которые пригодятся
нам для решения этой задачи.
TFileStream
- Create - создает экземпляр потока. Первый параметр - имя файла,
с которым нам предстоит работать. В качестве второго параметра
передаются флаги, определяющие метод работы с файлом. Я рассмотрю
только те флаги, которые нам пригодятся, информацию о других флагах
вы можете посмотреть в справочной системе Delphi.
- fmCreate - создает файл с указанным именем.
- fmOpenRead - открывает файл на чтение.
- fmOpenWrite - открывает файл на запись.
- Position - возвращает текущую позицию в потоке (количество
байт от начала данных).
- Size - возвращает размер потока в байтах.
- CopyFrom - копирует указанное число байтов из одного потока в другой.
- Free - Уничтожает объект и освобождает связанную с ним память.
Теперь посмотрим на функцию нарезки файла. Пусть вас не пугает ее размер,
все достаточно просто, да и комментариев тоже хватает.
(* Функция для нарезки файла *)
function CutFileStream( inFile, OutPath: string; outSize: Cardinal;
PB: TProgressBar = nil ): boolean;
var
inStream: TFileStream; // Входной файловый поток
outStream: TFileStream; // Выходной файловый поток
posStream: Cardinal; // Текущая позиция во входном потоке
fCount: Cardinal; // Количество выходных файлов
currentFile: Cardinal; // Номер текущего выходного файла
outFileName: string; // Шаблон выходного имени файла
longExt: Cardinal; // Количество цифр в расширении выходного файла
begin
// Если файл не найден, выходим
if not FileExists( inFile ) then
begin
Result := false;
Exit;
end;
// Выходной файл не может быть больше входного или равен ему
inStream := TFileStream.Create( inFile, fmOpenRead );
// Инициализируем переменные
Result := true;
posStream := 0;
currentFile := 1;
if PB <> nil then
begin
PB.Position := 0;
PB.Max := InStream.Size;
end;
// Узнаем количество выходных файлов
fCount := inStream.Size div outSize;
if ( inStream.Size mod outSize ) <> 0 then
Inc( fCount );
// В этом блоке, зная количество выходных файлов,
// мы определяем, сколько цифр должно быть
// в расширении выходного файла. Например:
// fCount < 10 => *.#f
// fCount < 100 => *.##f
// fCount < 1000 => *.###f и т.д.
longExt := Length( Format( '%d', [fCount] ) );
if OutPath[Length( OutPath )] <> '\' then
OutPath := OutPath + '\';
outFileName := OutPath + ExtractFileName( inFile ) + '.f%.' + IntToStr( longExt ) + 'd';
// Здесь происходит нарезка файла
try
repeat
OutStream := TFileStream.Create( Format( outFileName, [currentFile] ), fmCreate );
if ( InStream.Size - posStream ) < outSize then
posStream := posStream + OutStream.CopyFrom( InStream, InStream.Size - posStream )
else
posStream := posStream + OutStream.CopyFrom( InStream, outSize );
OutStream.Free;
if PB <> nil then PB.Position := posStream;
Inc( currentFile );
Application.ProcessMessages;
until posStream >= InStream.Size;
except
Result := false;
end;
if PB <> nil then PB.Position := 0;
inStream.Free;
end;
|
Начинаем разбираться. Первым делом проверяем наличие входного файла, и если
он не найден, благополучно выходим из процедуры. Создаем экземпляр потока,
открываем на чтение файл, который собираемся разрезать. Далее идет инициализация
переменных, пропустим это место. Затем идет определение количества выходных
файлов, остановлюсь на этом немного подробнее. Номер текущего выходного файла
записывается в его расширение, в дальнейшем, по этим номерам файлы будут
собраны. Сколько же цифр должно быть в расширении? Допустим 2. Тогда максимальное
количество выходных файлов не должно превышать 99. По идее этого вполне достаточно,
сложно представить ситуацию, когда придется "крошить" файл более мелко. Но
если все же этот предел будет превышен, мы неминуемо попадем в неприятную
ситуацию, связанную с повтором номеров. Тогда почему не увеличить количество
цифр, скажем до 3. Теперь максимальное количество выходных файлов не должно
превышать 999. Запас огромный (хотя его совсем не сложно преодолеть). А если
выходных файлов будет мало, то в начале расширения будут присутствовать
бесполезные нули, что с моей точки зрения выглядит не очень хорошо. К чему я
все это рассказал. С моей точки зрения подобный подход имеет два больших
минуса. Во-первых: слишком маленький предел мы задать не можем, велика
вероятность выхода за эти пределы, что приведет к печальным последствиям.
С другой стороны, большой предел приведет к тому, что в расширении будет
много цифр, а при небольшом количестве выходных файлов это будет смотреться
нелепо (например *.00001, *.00002 и т.д.). Во-вторых: программа просто
лишается универсальности. Именно поэтому я предпочитаю производить подобный
расчет для каждого нарезаемого файла. Завершает процедуру непосредственно
нарезка файла.
Половина дела сделана. Файл нарезан, теперь осталось собрать все части
файла в одно целое. Для начала подготовим список файлов, которые будем
объединять. Файлы будем выбирать при помощи OpenDialog1 с включенной
опцией ofAllowMultiSelect. Имена всех файлов помещаем в строковый массив
tmp. Рассмотрим, как подготавливается этот массив.
var
Form1: TForm1;
tmp: array of string;
{...}
(* Открываем файлы, которые нужно склеить *)
procedure TForm1.SelectStickFileClick(Sender: TObject);
var
i: integer;
begin
OpenDialog.Options := OpenDialog.Options + [ofAllowMultiSelect];
if OpenDialog.Execute then
begin
// Устанавливаем длинну массива
SetLength( tmp, OpenDialog.Files.Count );
// Заполняем массив
for i := Low( tmp ) to High( tmp ) do
tmp[i] := OpenDialog.Files.Strings[i];
// Обязательно сортируем
Sort( tmp );
// Выводим результат
for i := Low( tmp ) to High( tmp ) do
ListBoxForStick.Items.Add( tmp[i] );
EditStickDir.Text := ExtractFilePath( OpenDialog.FileName );
end;
end;
|
Здесь все просто и понятно, скажу пару слов о сортировке. Несмотря на то,
что в открытом диалоге все файлы упорядочены, не факт, что они упорядочены
и в OpenDialog1.Files.Strings. Если массив не отсортировать, результат
может оказаться непредсказуемым. Не стану приводить здесь описание функции
сортировки Sort, вы можете написать ее сами. Разберем функцию склейки файла.
(* Функция для склейки файла *)
function StickFileStream( inFiles: array of string; outDir: string;
PB: TProgressBar = nil ): boolean;
var
inStream: TFileStream; // Входной файловый поток
outStream: TFileStream; // Выходной файловый поток
outFileName: string; // Шаблон выходного имени файла
recFirstFile: boolean; // Признак записи первого файла
i: integer; // Цикловая переменная
begin
// Инициализируем переменные
if PB <> nil then
begin
PB.Position := 0;
PB.Max := GetSizeAllFiles( inFiles );
end;
Result := true;
recFirstFile := false;
outFileName := outDir + ExtractFileNameEx( inFiles[0] );
// Здесь происходит склейка файлов
try
for i := Low( inFiles ) to High( inFiles ) do
begin
inStream := TFileStream.Create( inFiles[i], fmOpenRead );
// При записи первой части файла выходной файл создаеитс,
// при записи всех последующих выходной файл открывается на запись
if recFirstFile then
outStream := TFileStream.Create( outFileName, fmOpenWrite )
else
begin
outStream := TFileStream.Create( outFileName, fmCreate );
recFirstFile := true;
end;
outStream.Position := outStream.Size;
outStream.CopyFrom( inStream, inStream.Size );
if PB <> nil then
PB.Position := outStream.Size + inStream.Size;
outStream.Free;
inStream.Free;
Application.ProcessMessages;
end;
except
Result := false;
end;
if PB <> nil then PB.Position := 0;
end;
|
В отличие от стандартной функции ExtractFileName, ExtractFileNameEx (ее
код приведен ниже) возвращает имя файла без расширения. Если необходимо
показать ход выполнения операции склеивания файлов в ProgressBar1, то
функция GetSizeAllFiles (она так же приведена ниже) позволит определить
размер конечного файла, а, следовательно, и ProgressBar1.Max.
(* Функция возвращает имя файла без расширения *)
function ExtractFileNameEx( fName: string ): string;
begin
if FileExists( fName ) then
begin
Result := ExtractFileName( fName );
Delete( Result, LastDelimiter( '.', Result ), Length( Result ) );
end
else
Result := '';
end;
(* Функция находит общий размер склеиваемых файлов *)
function GetSizeAllFiles( var x: array of string ): longint;
var
i: integer;
sr: TSearchRec;
begin
Result := 0;
for i := Low( x ) to High( x ) do
if FileExists( x[i] ) then
begin
FindFirst( x[i], faAnyFile, sr );
Result := Result + sr.Size;
FindClose( sr );
end;
end;
|
На сегодня это все. Удачи в программировании.
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|