PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » PureBasic для Windows » Поиск дубликатов файлов


Поиск дубликатов файлов

Сообщений 1 страница 30 из 49

1

Search duplicates
программа поиска дубликатов файлов

Скачать: yandex upload.ee

Скрин на линукс
https://i.imgur.com/wsdk7LY.png

Назначение
Поиск дубликатов файлов.

Использование
Бросить файлы и папки в окно программы или открыть используя кнопки. Далее нажать кнопку поиска, появится список файлов дубликатов по группам размеров. Далее нажать кнопку "Удалить", чтобы отмеченные галкой были удалены. При ошибках удаления файлов будет выдано сообщение, например для файлов только для чтения. Кнопка "Очистить" очищает оба списка и можно добавлять новые папки/файлы для проверки дубликатов.
У нижнего списка есть контекстное меню, чтобы открыть файл или его расположение, а также двойной клик открывает файл.

план
Добавить для Linux вариант поиска файлов с Find.
Добавить типы файлов в настройки.

Cтолкнулся с проблемой. Как сделать иконки в ListIconGadget в том числе и в колонках? Ранее у меня была отдельная колонка с иконкой замка чтобы запретить удалять с какой либо папки. В AutoIt3 используется структура LVITEM, но мне хотелось бы сделать это в Linux, то есть чисто средствами PureBasic сделать. Там есть одна функция, которая меняет иконку, но у неё нет параметра колонки из чего я делаю вывод, что иконка меняется только у первой колонки (и создаётся только у первой). Была ещё идея включить флаг чекбоксов и попробовать заменить иконку чекбокса своей иконкой, то есть у меня 3 положения как и у 3-х позиционного чекбокса, то есть замок открыт, закрыт с возможностью открыть, закрыт без возможности открыть. Либо отказаться от иконки и показывать запрет в виде -1, 0, 1 и дальше уровни приоритета до 9, хотя это не так очевидно/наглядно как иконки.

Примерно так должно быть: скрин

Отредактировано AZJIO (01.07.2022 23:11:55)

0

2

Может имеет смысл вместо ListIconGadget использовать таблицу на основе канваса?
Несколько примеров https://www.purebasic.fr/english/viewto … mp;t=72402
https://www.purebasic.fr/english/viewto … mp;t=54022

0

3

Когда писал подобную утилиту, остановился на
https://i115.fastpic.org/big/2021/0805/c1/9136eda28bea5c2a14f818a8e9c557c1.png

0

4

egons
Вы спутали темы, то была тема Синхронизация
_____________________________________________

Я тут поэкспериментировал и мне удалось создать карту списков. Изначально не понимал как добавлять элемент в список в уже существующий. Ну и конечно лишняя операция конвертирование размера файла в строку, чтобы использовать как ключ карты.

Код:
Structure Files
    List Path.s()
EndStructure

NewMap FilesPS.Files()


If AddElement(FilesPS()/Path())
	FilesPS()/Path() = 
EndIf
Код:
; Перебор файлов
tmp$ = aSePath(c) + sName
Size$ = Str(FileSize(tmp$))
If FindMapElement(FilesPS(), Size$) ; поиск делает элемент текущим, поэтому сразу добавляем в него элемент списка
	If AddElement(FilesPS()\Path())
    FilesPS()\Path() = tmp$
	EndIf
Else ; иначе если не существует
	If AddMapElement(FilesPS(), Size$, #PB_Map_NoElementCheck) ; добавляем элемент карты, потом элемент списка
    If AddElement(FilesPS()\Path())
    	FilesPS()\Path() = tmp$
    EndIf
	EndIf
EndIf

пока сделал для размеров, надо из них (совпавших по размеру) ещё делать для хеш-сумм.

В общем если есть другие идеи, смысл в том, что мне надо перебрать все файлы, их размеры и если размеры файлов одинаковые, то для этого элемента списка сделать подсписок файлов, то есть это говорит что они подозреваемые на одинаковость содержимого. И потом каждый подсписок получить хеш-суммы и перебрать по совпадению хеш-сумм, то есть создать список хеш-сумм с 2-мя и более файлами, в подсписке которых будут перечисляться пути имеющие эту хеш-суммы. Ну и потом добавлять это в ListIconGadget.

От сторонних модулей ListIcon я пока отказался, так как неизвестно как они будут работать при 1000-100000 файлов по памяти, утечкам и т.д.

Ещё надо встроить, чтобы добавляемое не было подэлементом уже содержимого, т.е. файл/папка внутри уже добавленной папки.
Надо поискать есть ли в Linux возможность создание подгруппы (в Windows есть), иначе придётся делать пустышку хоть и с чекбоксом, но в виде разделителя данных.

Отредактировано AZJIO (07.08.2021 09:42:51)

0

5

AZJIO написал(а):

каждый подсписок получить хеш-суммы и перебрать по совпадению хеш-сумм, то есть создать список хеш-сумм с 2-мя и более файлами, в подсписке которых будут перечисляться пути имеющие эту хеш-суммы.

Я бы сделал так

Код:
Structure SubFiles
  Path.s
  Hash.s
EndStructure

Structure Files
  Size.q
  List SubFiles.SubFiles()
EndStructure

NewList Files.Files()

0

6

Пётр
Hash.s мне нужен как ключ, чтобы сделать проверку уникальности, то есть когда я добавляю ключ в карту, он проверяет есть ли в нём этот элемент, иначе надо вручную делать поиск, на каждый шаг проверить есть ли он в списке. Думаю Карта в принципе делает тоже самое, но из-за того что надо переводить в строку и отсутствует сортировка это даёт мне отрицательный результат. Я бы мог получить в список структур и сортировать по размеру, а потом проверял бы кучность одноразмерных файлов. Ну то есть есть ещё смысл поэкспериментировать другими способами.
Я уже выложил скомпилированный вариант, то есть всё работает пока в упрощённом виде. Нет уровней запрета удаления и нет поддержки CSV. А просто накидать файлов и папок и получить список, в которых выделены дубликаты кроме 1-го. То есть работает, в том числе и удаление отмеченных и выдача ошибок (забыл снимать галки с успешно удалённых). Конт.меню есть чтобы открыть файл или расположение.

0

7

Что значит переводить в строку?у вас же наверняка не только текстовые файлы а в них и типа нуливые байты могут быть как признак конец строки?

0

8

Сравнил скорость карты и списка без отладчика. Причём, если я в список добавляю ещё одно поле структуры - хеш, то список ещё замедляется на 200 мс. Что пришлось тестировать? В карту добавляется размер Size$ = Str(FileSize(tmp$)), то есть ключ надо чтобы был как строка, в то время как в список я добавляю размер в типе число Size.q, то есть число 24789 в Size.q займёт 8 байтов, а в строке ключа Size$ 5 букв - 10 байтов, хотя если UTF8, то 5 байтов. То есть поиск сравнения размеров алгоритмически одинаков, что в карте что в списке, разница, что в карте это сделано внутренним алгоритмом, а в списке написаны циклом, но сути не меняет, т.е. сравнить с неким числом перебирая элементы списка/карты. В итоге карта отработала быстрее. Причём замена Size.q на Size.l, то есть 4 байта на число (2 Гб максимальный файл) не увеличили скорость.
4696 - список
845 - карта

Список

Код:
EnableExplicit

Structure Files
  Size.q
  List Path.s()
EndStructure

NewList FilesPS.Files()

Procedure FileSearch(List FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)

	Protected sName.s, c = 0, LenSPath, tmp$, Size.q
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)
	Protected fNotFind
	
	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)

	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf

	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
        tmp$ = aSePath(c) + sName
        Size = FileSize(tmp$)
        
        
        fNotFind = 1
        ForEach FilesPS()
            If FilesPS()\Size = Size
;             If AddElement(FilesPS())
            	If AddElement(FilesPS()\Path())
                FilesPS()\Path() = tmp$
            	EndIf
            	fNotFind = 0
            	Break
;             EndIf
        	EndIf
        Next
        If fNotFind
        	If AddElement(FilesPS())
            FilesPS()\Size = Size
            If AddElement(FilesPS()\Path())
            	FilesPS()\Path() = tmp$
            EndIf
        	EndIf
        EndIf
	
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
time$ = Str(ElapsedMilliseconds() - StartTime) ; вывод без отладчика
MessageRequester("", time$)
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер списка: " + Str(ListSize(FilesPS())) + #CRLF$

ForEach FilesPS()
	Debug #CRLF$ + Str(FilesPS()\Size)
	ForEach FilesPS()\Path()
    Debug FilesPS()\Path()
	Next
Next

Карта

Код:
EnableExplicit

Structure Files
	List Path.s()
EndStructure

NewMap FilesPS.Files()

Procedure FileSearch(Map FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)

	Protected sName.s, c = 0, LenSPath, tmp$, Size$
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)

	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)

	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf

	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
        tmp$ = aSePath(c) + sName
        Size$ = Str(FileSize(tmp$))
        If FindMapElement(FilesPS(), Size$) ; поиск делает элемент текущим, поэтому сразу добавляем в него элемент списка
        	If AddElement(FilesPS()\Path())
            FilesPS()\Path() = tmp$
        	EndIf
        Else ; иначе если не существует
        	If AddMapElement(FilesPS(), Size$, #PB_Map_NoElementCheck) ; добавляем элемент карты, потом элемент списка
            If AddElement(FilesPS()\Path())
            	FilesPS()\Path() = tmp$
            EndIf
        	EndIf
        EndIf
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
time$ = Str(ElapsedMilliseconds() - StartTime) ; вывод без отладчика
MessageRequester("", time$)
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер карты: " + Str(MapSize(FilesPS())) + #CRLF$

ForEach FilesPS()
	Debug #CRLF$ + MapKey(FilesPS())
	ForEach FilesPS()\Path()
    Debug FilesPS()\Path()
	Next
Next

0

9

Из-за доступа к диску результат будет искажен. ОС кеширует доступ к файлам и первое сканирование будет идти дольше чем второе.

0

10

Пётр

Из-за доступа к диску результат будет искажен. ОС кеширует доступ к файлам и первое сканирование будет идти дольше чем второе.

Это я в курсе, поэтому я всегда начинаю тестировать со второго раза, когда файловые адреса текущего поиска заполняют память диска и когда объём данных не слишком велик, чтобы не поместиться в кеше. То есть я тестирую чистую скорость алгоритма. То есть при 5-краном запуске у меня разброс плюс/минус 1-2%, то есть 4700-4750, а пятикратный запуск 840-856, ну это не может быть совпадением такая кучность при разных алгоритмах.

0

11

Протестировал на папке Windows.
List - 15 секунд.
Map - 10 секунд.

0

12

Удалось обработать быстро списком, преимущество, что теперь есть сортировка по размеру, а в карте не было.

Смысл в следующем: после сортировки одинаковые размеры идут друг за другом. Поэтому можно проверить если старый элемент совпадает новому в цикле, то идут перечисление одинаковых размеров и можно сформировать список: позиция с которой началось повторение размеров и количество этих совпадений., то есть структура (Pos, Count), далее второй проход по этому списку уже получает группы путей и можно уже формирование MD5 тем же методом. И самое главное скорость 725 мс, то есть быстрее карты.

До этого я не мог понять как мне получить предыдущий элемент, был бы массив я бы просто сравнивал arr(i) и arr(i-1), но опять проблема что массив не может содержать разные типы размер и путь. Подумал может сделать два массива с разными типами, но тогда не отсортировать. Про массив структур я забыл, поэтому сделал просто кеширование предыдущего элемента списка (поле размер)

Код:
EnableExplicit

Structure Files
	Size.q
	Path.s
EndStructure

Structure DataLst
	Pos.i
	Count.i
EndStructure

NewList FilesPS.Files()
NewList DataLst.DataLst()

Procedure FileSearch(List FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)
	
	Protected sName.s, c = 0, LenSPath
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)
	
	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)
	
	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf
	
	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
;         tmp$ = aSePath(c) + sName
        If AddElement(FilesPS())
        	FilesPS()\Path = aSePath(c) + sName
        	FilesPS()\Size = FileSize(FilesPS()\Path)
        EndIf
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$, i, tmp
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
; SortList(FilesPS(), #PB_Sort_Ascending)
SortStructuredList(FilesPS(), #PB_Sort_Ascending , OffsetOf(Files\Size) , TypeOf(Files\Size))
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер списка: " + Str(ListSize(FilesPS())) + #CRLF$

Define Count, open
i = 0
open = 0
tmp = -3
ForEach FilesPS()
	If tmp = FilesPS()\Size
    open = 1
    Count + 1
    If Count = 1
    	If AddElement(DataLst())
        DataLst()\Pos = i
    	EndIf
    EndIf
	Else
    If open = 1
    	DataLst()\Count = Count + 1
    EndIf
    open = 0
    Count = 0
	EndIf
	tmp = FilesPS()\Size
	i + 1
Next
If open = 1
	DataLst()\Count = Count + 1
EndIf

Define x, Point, Point2
i = 1
If ListSize(DataLst()) = 0 Or ListSize(FilesPS()) = 0
	End
EndIf
ResetList(DataLst())
ResetList(FilesPS())
NextElement(DataLst())
NextElement(FilesPS())
Repeat
; 	Debug "i = " + Str(i) + " Pos = " + Str(DataLst()\Pos)
	If i = DataLst()\Pos
    For x = 1 To DataLst()\Count
    	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
    	If Not NextElement(FilesPS())
        Break
    	EndIf
    	i + 1
    Next
    Debug "_________________________"
    If Not NextElement(DataLst())
    	Break
    EndIf
	Else
    i + 1
    If Not NextElement(FilesPS())
    	Break
    EndIf
	EndIf
; 	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
ForEver
Debug "_____КОНЕЦ________"
Debug "_________________________"
Debug "_________________________"
time$ = Str(ElapsedMilliseconds() - StartTime)
MessageRequester("", time$) ; вывод без отладчика

i = 0
ForEach FilesPS()
	i + 1
	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
Next

ForEach DataLst()
    Debug Str(DataLst()\Pos) + " " + Str(DataLst()\Count)
Next

0

13

AZJIO написал(а):

До этого я не мог понять как мне получить предыдущий элемент

PreviousElement.

0

14

Пётр
Это понятно, но тогда на каждом шаге цикла надо делать один раз назад и 2 раза вперёд, то есть оперировать указателями, которые сами по себе число int, так не проще ли на каждом шаге кешировать этот int в переменную, число шагов будет меньше. То есть сама конструкция цикла становится сложнее, какие то ResetList, NextElement, то есть пройтись по списку. В данном случае с массивом я бы просто сразу обсчитывал DataLst() перешёл допустим к элементу 800 и снял бы данные с 800 по 805, а со списком мне нужно именно 800 раз делать NextElement, захватить данные и потом сдвигать текущий элемент дальше, то есть именно пройтись по всему списку, даже если в миллионе элементов их будет всего 2, а в массиве я бы сразу взял конкретно эти 2 элемента. Только сейчас и сам понимаю что нужно переделывать на массив.
Ну и для списка была мысль, что надо не позицию элемента сохранять, а указатель на элемент и с него двигать дальше, тогда да, будет аналогично массиву, хотя читать этот код сложнее. Ну или использовать SelectElement().

Упростил решение

Код:
ForEach DataLst()
	SelectElement(FilesPS(), DataLst()\Pos - 1)
	For x = 1 To DataLst()\Count
    Debug Str(DataLst()\Pos - 1) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
    NextElement(FilesPS())
	Next
	Debug "_________________________"
Next
Debug "_____КОНЕЦ________"
Debug "_________________________"
Debug "_________________________"

Отредактировано AZJIO (10.08.2021 15:37:01)

0

15

Если кто желает попробовать новый исходник, то добавил в архив основным (по ссылке в шапке), но скомпилированные оставлены пока со старым исходником, так как ещё надо потестить может оптимизировать в функции участки кода. Что мне понравилось в отличии от предыдущего исходника с картами. Во первых и в главных, когда идет перебор элементов по размеру и добавление в карту очередной хеш-суммы мне показалось неправильно сверять его с кучей существующих, так как если хеш снят с группы из 5 файлов в 10 байт, то он явно не будет совпадать с другим хешем с размером файла 20 байт. То есть мне надо проверять не по миллионной базе хешей, а всего по 5-элементной базе, только из этой кучи возможно совпадение хешей. А это заметное торможение. Теперь же делается сортировка и поиск условно говоря по 5-ти элементному списку и если есть результат, то добавляется в GUI, если нет то список очищается и собирается новый из новой группы, естественно поиск среди 2-5 элементов в геометрической прогрессии меньше в случае если обсчитывается много файлов.

На линукс пока есть проблемы, ещё ранее предыдущая версия захватывала элементы с размером -1 (не существует) или -2 (папка), подозреваю что проблема файл-ссылок. То есть обсчёт "/home/user" не могу дождаться завершения, и датчики работы диска и проца не показывают активность. А на средних выборочных папках (Загрузки - 26 Гб, 3700 файлов) отрабатывает мгновенно.

Тест нового исходника на больших папках выдаёт проблему, в то время как старая на Map работает.
Ещё тест, показал, что не работает, когда функция отправлена в другой поток, чтобы была возможность отправлять сообщения о процессе в строку состояния. В то время как вариант с Map тоже иногда сбоит, если в другом потоке.

Отредактировано AZJIO (10.08.2021 23:35:59)

0

16

AZJIO написал(а):

а со списком мне нужно именно 800 раз делать NextElement

а про это забыл?
SelectElement()

но массив наверно будет быстрей листа

0

17

newJS написал(а):

а про это забыл?
SelectElement()

Забыл, но на несколько минут (позавчера), точнее просто посмотрел функции List и увидел возможный вариант. Уже полностью сделал.
Вчера сделал установщики для Linux, потому что многие не любители проверять, они понимают только как пакет, который кликнув установился и появился в меню. А для Windows сделал отдельный исходник с отправкой поиска дубликатов в отдельный поток, так как в Windows нет проблемы с потоком. Жалко что в Linux есть, потому что я не могу в промежутках отправить текст в строку состояния, она будет обновлена как только процесс по событию поиска закончится. И самое интересное что в AutoIt3 если я отправляю в строку состояния текущее выполнение части алгоритма это не блокирует окно, не понимаю, почему в PureBasic блокируется окно как зависшее, это можно проверить на моей аналогичной программе на AutoIt3, учитывая что он имеет потоков, всё в одном.

0

18

AZJIO написал(а):

в Windows нет проблемы с потоком. Жалко что в Linux есть

Если не работать с GUI в потоках, проблем обычно нет.

0

19

А есле сделать что то типа компонента со своим обработчиком и в этот обработчик слать типа асинхронного сообщения?

0

20

AZJIO написал(а):

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

А если дополнительно сверять тип файла? Или подсчитать хэш быстрее?

0

21

Sergeihik написал(а):

в этот обработчик слать типа асинхронного сообщения?

Для этого есть PostEvent.

0

22

Попробуйте применить https://ru.wikipedia.org/wiki/Фильтр_Блума

На английском форуме
https://www.purebasic.fr/english/viewto … 84#p573484

0

23

Пётр написал(а):

Для этого есть PostEvent.

Postevent ,название как бы намекает что это сообщение после завершения цикла обработчика?
А не получение события во время работы обработки.

0

24

https://www.purebasic.com/documentation … event.html

0

25

AZJIO написал(а):

Забыл, но на несколько минут (позавчера), точнее просто посмотрел функции List и увидел возможный вариант. Уже полностью сделал.
Вчера сделал установщики для Linux, потому что многие не любители проверять, они понимают только как пакет, который кликнув установился и появился в меню. А для Windows сделал отдельный исходник с отправкой поиска дубликатов в отдельный поток, так как в Windows нет проблемы с потоком. Жалко что в Linux есть, потому что я не могу в промежутках отправить текст в строку состояния, она будет обновлена как только процесс по событию поиска закончится. И самое интересное что в AutoIt3 если я отправляю в строку состояния текущее выполнение части алгоритма это не блокирует окно, не понимаю, почему в PureBasic блокируется окно как зависшее, это можно проверить на моей аналогичной программе на AutoIt3, учитывая что он имеет потоков, всё в одном.

Во первых - спасибо за прогу.
Во вторых протестировал виндовый исходник с потоками на дебиан и убунту - работают отлично - пишут в строку состояния во время работы в потоке +Добавил удаление отмеченных для удаления из #ListIcon2 + заменил поле #StatusBar на настоящий StatusBar.

0

26

Обновил, версия 0.9.
Новый алгоритм на картах, всвязи с тем что мне стало более ясно как работать с картами.

Пётр написал(а):

Я бы сделал так

Structure Files
  Size.q
  List SubFiles.SubFiles()
EndStructure

Да, мой код стал похож на такую конструкцию. Я только сейчас обратил на это внимание, когда в голове продумал алгоритм положил его на "бумагу".
Я получаю файлы и создаю подсписок в структуре одинаковых файлов с общим размером . Далее я практически также делаю, но уже с хешем. Создавая карту мне надо размер указывать как строку-ключ, поэтому я делаю поле в структуре для размера. А при создании MD5 я уже использую MD5 как ключ, но всё равно мне нужен размер в структуре, для вывода в результаты и получается, что я 2 раза использую одну и туже структуру.
Новое открытие для себя началось с того что если я не могу сортировать карту, то надо как то данные перенести в с список, ага, перебрать все элементы, потом озарила мысль, что эта структура и мне нужен всего лишь список указателей, который я сортирую по размеру и буду обращаться по указателю. Но в карте нет указателей, даже если я использую NextMapElement, то как я потом выделю текущий указатель. Потом я подумал копировать структуру CopyStructure() и это сработало. Потом подумал а зачем мне копировать структуру, нельзя ли обращаться к ней напрямую, то есть я просто приравнял элемент карты к списку, типа ListMD5() = MapMD5(), то есть в цикле перебираю элементы карты, добавляю элемент списка и приравниваю, и это сработало. И пошёл ещё дальше, так как у меня структуры одинаковые, то я получив карту по размеру тупо приравниваю элементы списка из одной карты в другую карту. Так как у них получается общий указатель на одну и туже структуру, я не могу очистить использованную карту размеров, так как она утянет за собой структуры новой карты, но главное не тратится время на копирование тупо игра с указателями не больше.
Ну теперь мне стало легко сделать сортировку списка и добавил сортировку по "уровню удаления", теперь оно работает, проверено. Например кидаем разные папки в верхнее окно (тестовая папка сделать копии папки, чтобы нашлись дубликаты), выбираем у папок уровни -2 или -1 или 0 или 1 или 2 делаем поиск и получаем, что -2 и  -1 не отмечаются галочками, а -2 невозможно поставить галочку, а если и поставить как то, то всё равно удаление этого файла не сработает. Если ставить между уровнями 0-2, то файлы в результатах выстроятся таким образом что удалятся будет в первую очередь из папки с уровнем 2, ведь у нас оставляется один дубликат, так вот с уровнем 2 будет нижним в списке, то есть приоритет на удаление. Грубо говоря папку на флешке можно поставить уровень 2 так как логичнее что правильнее оставить копию на жёстком диске, или есть некая сортированная папка разложенная по полочкам фото, и есть куча на флешке где время от времени копировал что-то показать, так конечно упорядоченная папка будет с меньшим приоритетом на удаление, а мусорная-временная с максимальным приоритетом.
Потом ещё сделаю файл-список, смысл которого, например есть файлы которые однажды проверил что их надо удалить, а они с мусором попали на флешку и ещё куда, так подключил CSV-файл ранее сохранённый и всё что в нём есть считавшийся мусором можно проверить есть ли такое же на флешке (по MD5) и уже перебирать фото на флешке не придётся вычищая опять этот же мусор, так как их хеши уже есть в CSV и можно нажимать "удалить".
Ну и ещё в линуксе использовал StatusBarText, типа настоящий, текст в нём также появляется только после завершения функций, а в винде я помню что работало нормально.
Ещё проблема, есть сериал на 200 серий, в нём 158 серий с одинаковым размером до байта, но 420 Мб каждая серия, естественно что вычислять MD5 этих файлов оказалось расточительное занятие для алгоритма. А значит понятно что надо придумывать перед получением MD5 для файлов более 10 МБ можно сравнить первые 2 Мб или по 100 байт но из разных позиций, в центре и в конце, и только потом приступать вычислять MD5. Вот только надо придумывать идентификатор в карту, разве что эти байты считать идентификаторами.

Отредактировано AZJIO (23.06.2022 05:41:04)

0

27

AZJIO написал(а):

Но в карте нет указателей

Код:
NewMap Country.s()

Country("US") = "United States"
*p1.string = @Country()

AddMapElement(Country(), "")
Country("FR") = "France"
*p2.string = @Country()

Debug *p1
Debug *p2
Debug *p1\s
Debug *p2\s
AZJIO написал(а):

Так как у них получается общий указатель на одну и туже структуру

Как копируется указатель? Могу предположить что копируются данные, а не указатель на них.
Это копирование значения

Код:
ListMD5() = MapMD5()

А это указателя

Код:
*ListMD5() = @MapMD5()
AZJIO написал(а):

значит понятно что надо придумывать перед получением MD5 для файлов более 10 МБ можно сравнить первые 2 Мб или по 100 байт но из разных позиций, в центре и в конце, и только потом приступать вычислять MD5. Вот только надо придумывать идентификатор в карту, разве что эти байты считать идентификаторами.

Можно вычислить MD5 для фрагмента и хранить его.
Вообще скорость вычисления MD5 довольно высокая. У меня она составляет около 200 МБ/с. В реальности должно быть больше, т. к. скорость упирается не в процессор, а в винт. Для SSD думаю реально достичь скорости 500 МБ/с. Если распараллелить задачу то на многоядерных процессорах и скоростном SSD можно достичь еще большей скорости хеширования.
Скорость хеширования можно проверить этой утилитой https://www.cyberforum.ru/beta-testing/ … 76230.html

0

28

Пётр
Скорость вычисление md5 высокая, но скорость чтения 60 ГБ всё равно требует времени. Вот только читать эти 60 Гб не обязательно. Когда мы смотрим кино 8 Гб мы переставляем в центр или в конец и плеер тут же воспроизводит из текущей позиции, значит скорость перехода на позицию в файле мгновенная, значит легко написать формулу, в которой делим размер файла на 32, получаем например по 100 Мб для фильма 3,2 Гб, теперь двигаем указатель в файле по 100 Мб и получаем по байту с каждой позиции и назовём этот код MD0, то есть это будет предварительное грубое сравнение, которое исключит чтение всего файла. Может это единственный случай, который в 99% ни у кого не случится, но всё же исключить это хочется. Как быстрое решение я сделал вывод информации, если данных более 100МБ то сообщить пользователю о размере данных, для которых будет вычисляться МД5 и пока не сделал но хочу сделать сортировку и вывести 3 топовых файла по размеру, чтобы пользователь знал, что некую папку можно пока исключить из обработки, то есть если я сравниваю папку с видео-контентом, там при терабайте данных я мгновенно получаю 0 дубликатов и конечно не хотел бы этих ложных вычислений. На счёт скорости SSD для видео-контента пока дорогие а на шпидельном помню что 200 Гб копировал с диска на диск очень долго (в реальных условиях), не менее часа (как бы не 4 часа), поэтому даже если мне 20 минут ждать вместо 1-й секунды я уже не согласен.

Указатели то есть, но как я могу например сделать SelectElement() и потом прочитать текущий MapMD5(), то есть я бы устанавливал текущий по сортированному списку указателей. Ну а раз я могу обратится без установки текущего, как у меня получилось то и не стал усложнять.

Пётр
Это копирование значения
Код:

ListMD5() = MapMD5()

Как я понимаю карта при создании структур хранит указатель на структуру, а если я приравниваю, то по факту я думаю что присваиваю указатель, а так как у обоих структур одинаковые данные, то при обработке движок думает что это его структура, то есть формально всё соблюдено, есть указатель, есть данные структуры для этого указателя и соответственно все работает. Копирование значения, коим является указатель. А копирование структуры не зря же это выполняется конкретной функцией.

Отредактировано AZJIO (23.06.2022 15:32:38)

0

29

AZJIO
я бы попробовал такой агло:
если размер совпадает - получаю md5 65кб в конце файла (типа FastMD), если и они совпадают тогда уже считал бы md5 всего файла
и так для всех файлов в не зависимости от размера..

0

30

Нашёл ещё слабости алгоритма: когда я получаю MD5 сверяю его со всей картой, а надо среди группы одноразмерных файлов. Сейчас у меня 250 Мб мурыжил несколько минут, учитывая 27 тысяч файлов и 24 тыс. дубликатов, понятно, что MD5 одного дубликата сравнивать с 24 тыс. строк MD5 это будет тормозить. Получается надо иметь временную карту для обработки одноразмерных, а результаты в общий список на вывод.

Сейчас добавил сохранение CSV всех файлов (перезалил, см. исходник), а то ранее я сделал только дубликатов с результата, удачно получилось разветвить функцию сравнения, если флаг сохранения CSV, то функция сравнения сделает список и выпрыгнет.

Lin написал(а):

в конце файла (типа FastMD)

а что конец не может совпадать? Те же субтитры в конце фильма вклеют без перекодирования и будет один в один. Я вроде сказал про алгоритм, который не тормозит, если выборочно по байту взять везде равномерно. Если брать везде то будет защита от склеек.

Lin написал(а):

в не зависимости от размера

надо тестить, возможно это действительно будет работать быстрее.

Отредактировано AZJIO (23.06.2022 18:48:58)

0


Вы здесь » PureBasic - форум » PureBasic для Windows » Поиск дубликатов файлов