EnglishРусский  

   hello

   square

   easymath

   runini

   easyhtml

   calendar

   samefiles

   Продолжение следует

Данный проект закрыт! Создан новый скриптовый язык с тем же именем. Всё доступно на GitHub.
Также попробуйте нашу open source кроссплатформенную программу для создания и управления скриптами.

Реклама

Инсталлятор CreateInstall
Бесплатные и коммерческие инсталляторы

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 равным нулю в том случае, если выше родительской директории нет. То есть директория не может иметь нулевой индекс.

Упражнение 2

Написать программу нахождения одинаковых файлов на всех локальных жестких дисках компьютера.

Исходники

Редактировать