samefiles
В этом уроке предлагаю поработать с файлами. Давайте попробуем найти одинаковые файлы на Вашем компьютере. Причем сделаем задачу более интересной и полезной - будем находить одинаковые файлы не по их названию, а по содержанию. Я думаю, что результаты работы программы вас сильно удивят.
Пример 1
Необходимо найти в указанной папке или на диске все одинаковые по содержанию файлы.Задача на первый взгляд будет выполнятся очень длительное время. Попробуем придумать алгоритм, который упростит выполнение задания. Если у файлов разные размеры, то они не могут быть равными. Исходя из этого предположения можно в начале получить имена и размеры всех сравниваемых файлов, затем отсортировать их по размеру и после этого сравнивать только файлы с одинаковым размером.
C помощью команды type опишем структуру для хранения необходимой информации. Для экономии памяти будем хранить не весь путь, а только имя файла. Вместо полного имени в поле owner будет хранится индекс родительской директории в массиве директорий.
type finf
{
str name
uint size
uint owner
}
Вот какие глобальные переменные нам понадобяться:
global
{
arr dirs of finf
arr files of finf
arr sizes of uint
str output
}
dirs - массив обработанных директорий.
files - массив файлов.
sizes - массив индексов для files, который мы будем сортировать.
output - строка для вывода результатов.
Следующие две функции отвечают за добавление в массивы директорий и файлов.
func uint newdir( str name, uint owner )
func uint newfile( str name, uint size owner )
С их кодом можно познакомиться в исходнике. Там добавляется один элемент к массиву и заполняются его поля.
Функция scanfolder находит все директории и файлы по указанному пути. В случае нахождения директории, добавляется элемент в массив dirs, он становится родительским и заново вызывается функция сканирования. Если находим файл, то добавляем элемент в массив файлов. Для упрощения задачи я исключаю из рассмотрения файлы больше 4GB, условие !cur.sizehi служит как раз для этого.
func scanfolder( str wildcard, uint owner )
{
...
if cur.attrib & $FILE_ATTRIBUTE_DIRECTORY
{
scanfolder( cur.fullname + "\\*.*", newdir( cur.name, owner ))
}
...
}
Функция scaninit на основании начального пути сканирования формирует вызов scanfolder. Модифицируя эти функции вы можете использовать различные маски для поиска файлов, а так же указывать верхние и нижние границы размера сравниваемых файлов.
func scaninit( str folder )
{
str wildcard
folder.fdelslash()
@"Scanning \( folder )\n"
scanfolder( (wildcard = folder ).faddname( "*.*" ), newdir( folder, 0 ))
}
Второй этап заключается в сортировке полученных данных. Конечно, можно сортировать массив files, но для ускорения лучше создать отдельный массив с индексами на files и сортировать его. Сортировка, как правило, требудет много перемещений элементов и чем меньше размер одного элемента массива, тем лучше.
func int sortsize( uint left right )
{
return int( files[ left->uint ].size ) - int( files[ right->uint ].size )
}
func sortfiles
{
uint i
@"Sorting...\n"
sizes.expand( *files )
fornum i, *sizes : sizes[ i ] = i
sizes.sort( &sortsize )
}
В функции sortfiles заполняем массив sizes индексами на files. Первоначально индекс совпадает с порядковым номером. После этого сортируем массив sizes используя функцию сортировки sortsize. Параметры left и right являются указателями на данные. Если бы у нас в массиве были структуры, то мы бы использовали их как объекты, но так как у нас массив sizes состоит из uint, то мы берем значение элемента с помощью ->uint. files[ index ].size возвращает размер соответствующего файла. Сравнивая размеры возвращаем значение меньше, равное или больше нуля, если левый сравниваемый файл меньше, равен или больше правого.
Функции getdir и getfile восстанавливают полное имя файла по значению поля owner. getdir доходит рекурсивно до первой родительской директории и возвращаясь обратно создает полный путь.
func str getdir( uint id, str ret )
func str getfile( uint id, str ret )
Сейчас приступим к разбору главной функции сравнения. Проходим в цикле по всем отсортированным файлам начиная с минимального размера.
func compare
{
...
fornum i, *sizes - 1
{
id = sizes[ i ]
if !*files[ id ].name : continue
Здесь мы игнорируем файлы, которые уже являются дубликатами. В дальнейшем мы у всех найденных дубликатов будем обнулять имя файла.
found = 0
next = sizes[ j = i + 1 ]
while files[ id ].size == files[ next ].size
{
В данном цикле мы сравниваем текущий файл со всеми следующими файлами имеющими такой же размер. При этом пропускаем уже определенные файлы-дубликаты. Сравнение производим с помощью функции isequalfiles из стандартной библиотеки. При обнаружении совпадения выводим информацию в строку output.
if *files[ next ].name &&
isequalfiles( getfile( id, idname ), getfile( next, nextname ))
{
if !found
{
output @ "\lSize: \(files[ id ].size) ========\l\( idname )\l"
}
count++
( output @ nextname ) @"\l"
found = 1
files[ next ].name.clear()
}
if ++j == *sizes : break
next = sizes[ j ]
}
Это фрагмент служит для вывода промежуточных результатов. i & 0x3F определяет вывод результата после каждого 64-го файла.
if i && !( i & 0x3F )
{
@ "\rApproved files: \(i) Found the same files: \(count)"
}
}
...
}
В функциях
func init
func search
func main<main
нет ничего сложного. Так как количество файлов и директорий может быть большим, то мы в функции init заранее резервируем место для некоторого начального количества элементов. И кроме этого создаем один пустой родительский элемент в массиве dirs, чтобы нумерация директорий начиналась с 1. Дело в том, что мы считаем поле owner равным нулю в том случае, если выше родительской директории нет. То есть директория не может иметь нулевой индекс.