Что нового

[Файловая система] Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

kosjachok

Новичок
Сообщения
30
Репутация
3
Столкнулся с проблемкой...
В папке находится около 3000 архивов...
В каждом архиве находится около 100 файлов.... - они периодически добавляются - убавляются...
Мне нужно периодически подсчитывать общее кол-во файлов во всех архивах...
Реализовал след. алгоритм:
Код:
#Include <7Zip.au3>
#include <file.au3>
#include <GUIConstantsEx.au3>
#include <ProgressConstants.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

$Form1 = GUICreate("Подсчет документов", 507, 70, 192, 124)
$Progress1 = GUICtrlCreateProgress(8, 32, 489, 9)
$Label1 = GUICtrlCreateLabel("Кол-во доков", 8, 8, 36, 17)
$Button1 = GUICtrlCreateButton('Go', 8, 45, 30, 17)
GUISetState(@SW_SHOW)

While 1
	$nMsg = GUIGetMsg()
	Switch $nMsg
		Case $GUI_EVENT_CLOSE
			Exit
		Case $Button1
			   $DirUpd = FileSelectFolder('Выбери папку с архивами', "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}")
			   If @error Then
			   MsgBox(4096,"Error","Директория не выбрана!")
				  Else
				  FileChangeDir($DirUpd)
				  $search = FileFindFirstFile("*.zip") 
				  $nCount = 0
				  $PosPogr = 0
				  $DPos = 100/3992
				  While 1
					  $file = FileFindNextFile($search) 
					  If @error Then ExitLoop
   				         $nFile = _7ZipGetFileCount($DirUpd & '\' & $file)
			    	         $nCount = $nCount + $nFile
					 GUICtrlSetData ($Label1, "В архиве файлов: " & $nCount)
					 $PosPogr = $PosPogr+$DPos
					 GUICtrlSetData ($Progress1, $PosPogr)
				  WEnd
				  FileClose($search)
				  MsgBox(4096, "Damp:", 'В архиве файлов: '& $nCount)
			   EndIf
EndSwitch
WEnd

проблема в том что процесс подсчета занимает продолжительное время...
Как можно улучшить алгоритм , чтобы ускорить процесс...
- как вариант подумал отслеживать только изменившиеся архивы и пересчитывать файлы только в них... но опять же - возникает вопрос как это делать... - поиск по массиву затем сравнение СРС? или даты изменения?, или размера? что быстрее???
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Это только кусок кода, причем нерабочий. Для начала, я бы заменил все это на _FileListToArray(). А на счет выборочной проверки файлов, можешь установить время изменения файла (Modified) на одну секунду меньше времени создания (Created). А затем проверять это.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,666
Репутация
2,463
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Если в папке только нужные архивы, то можно использовать DirGetSize:

Код:
$aDirSize = DirGetSize($DirUpd, 1 + 2)
				$nCount = $aDirSize[1]
				MsgBox(4096, "Damp:", 'В архиве файлов: ' & $nCount)


а для получения списка файлов на форуме где то велось обсуждение, поищите по функции _FileListToArrayEx.
 

dwerf

Использует ArchLinux
Сообщения
478
Репутация
219
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

CreatoR [?]
Если в папке только нужные архивы, то можно использовать DirGetSize:

Это будет колличество архивов в папке, а не колличество файлов в архивах.
Или я чего то не уловил?
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,666
Репутация
2,463
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

dwerf [?]
Это будет колличество архивов в папке, а не колличество файлов в архивах.
Или я чего то не уловил?
Ну я как бы тоже не уловил, но в примере у автора темы нет подсчёта файлов в архиве, а только подсчёт файлов в папке, отсюда и предположение что под архивами подразумивается папки (архивов). Кстати архив не обязательно должен быть запакованным файлом ;).
 
Автор
K

kosjachok

Новичок
Сообщения
30
Репутация
3
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Yashied сказал(а):
Это только кусок кода, причем нерабочий.
Приношу свои извенения, случайно удалил с комментариями строку, уже исправил код

CreatoR сказал(а):
dwerf [?]
Это будет колличество архивов в папке, а не колличество файлов в архивах.
Или я чего то не уловил?
Ну я как бы тоже не уловил, но в примере у автора темы нет подсчёта файлов в архиве, а только подсчёт файлов в папке, отсюда и предположение что под архивами подразумивается папки (архивов). Кстати архив не обязательно должен быть запакованным файлом ;).
Опять приношу извинения, нужно подсчитать кол-во файлов именно запакованных в 3000 архивах,
Код:
$nFile = _7ZipGetFileCount($DirUpd & '\' & $file)
                             $nCount = $nCount + $nFile
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

А где мне взять 7Zip.au3?
 
Автор
K

kosjachok

Новичок
Сообщения
30
Репутация
3
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Yashied сказал(а):
А где мне взять 7Zip.au3?
7Zip.au3:
http://www.autoitscript.com/forum/index.php?showtopic=85094&st=0&p=610129&hl=crc%20udf&fromsearch=1&#entry610129
и к нему идет 7_zip32.dll
http://www.autoitscript.com/forum/index.php?showtopic=25984&st=0&p=183153&#entry183153
Для работы скрипта 7_zip32.dll нужно переименовать в 7-zip32.dll и положить в каталог со скриптом (или подправить UDF)
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Да, UDF уже не оптимизируешь. А как на счет разницы в датах из моего предыдущего поста? По идее, это IMHO самый оптимальный вариант.


Добавлено:
Сообщение автоматически объединено:

Да, и пожалуйста не нужно "руссифицировать" сокращения, т.е. "УДФ" вместо UDF и т.д. Это у нас написано в правилах.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,666
Репутация
2,463
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Вот так должно быть быстрее:

Код:
#include <GUIConstantsEx.au3>
#include <ProgressConstants.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

#include "7Zip.au3"

$Form1 = GUICreate("Подсчет документов", 507, 70, 192, 124)
$Progress1 = GUICtrlCreateProgress(8, 32, 489, 9)
$Label1 = GUICtrlCreateLabel("Кол-во доков", 8, 8, 36, 17)
$Button1 = GUICtrlCreateButton('Go', 8, 45, 30, 17)

GUISetState(@SW_SHOW)

While 1
	$nMsg = GUIGetMsg()
	
	Switch $nMsg
		Case $GUI_EVENT_CLOSE
			Exit
		Case $Button1
			Dim $aRecords[10][2]
			$DirUpd = FileSelectFolder('Выбери папку с архивами', "", 0, "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}")
			
			If @error Then
				MsgBox(4096, "Error", "Директория не выбрана!")
			Else
				$nCount = 0
				$PosPogr = 0
				$DPos = 100 / 3992
				
				$aFiles = _FilesList($DirUpd, "*.zip")
				
				For $i = 1 To $aFiles[0]
					$nCount += _7ZipGetFileCount($aFiles[$i])
					$PosPogr += $DPos
					GUICtrlSetData($Label1, "В архиве файлов: " & $nCount)
					GUICtrlSetData($Progress1, $PosPogr)
				Next
				
				MsgBox(4096, "Damp:", 'В архиве файлов: ' & $nCount)
			EndIf
	EndSwitch
WEnd

Func _FilesList($sPath, $sFileMask)
	Local $sOut, $aOut, $hDir

	$sOut = StringToBinary("0" & @CRLF, 2)
	$hDir = Run(@ComSpec & ' /U /C DIR "' & $sPath & '\' & $sFileMask & '" /S /B /A-D', @SystemDir, @SW_HIDE, 6)

	While 1
		$sOut &= StdoutRead($hDir, False, True)
		If @error Then ExitLoop
	WEnd

	$aOut = StringRegExp(BinaryToString($sOut, 2), "[^\r\n]+", 3)
	If @error Then Return SetError(1)

	$aOut[0] = UBound($aOut) - 1
	Return $aOut
EndFunc
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

CreatoR сказал(а):
Вот так должно быть быстрее...

У меня к сожалению (?) нет столько .zip файлов, но скорее всего здесь "тормозом" является именно _7ZipGetFileCount(). Поэтому нужно проверять архивы избирательно.

_FilesList() здесь "погоду" не делает (я думаю).
 
Автор
K

kosjachok

Новичок
Сообщения
30
Репутация
3
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

CreatoR сказал(а):
Вот так должно быть быстрее
Действительно, - посчитало кол-во файлов за 7 минут, вместо 12 как раньше...
Я вот думаю, может реализовать 1-й подсчет - полный, всех архивов,
а последующие - только пересчёт изменившихся и добавленных...
Ведь загонять данные придется в массив,
- загружаем массив из файла
- в массиве (3000 строк) ищемь архив по имени в массиве (это вроде тоже тормоз???)
- сравниваем дату архива(или может что другое???) и сохраненную
- если совпадает - значит ищем следующий файл
- если не совпадает - считаем кол-во файлов в архиве и обновляем количество в массиве
- если в массиве файл не найден - значи считаем кол-во файлов в архиве и добавляем в массив
- суммируем массив по столбцу с количеством файлов в архивах - получаем сумму файлов...

Во всем этом смущает - поиск по массиву ... Где - то слышал что эта операция тоже замедляет скрипт...
Как быть?
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

У меня получилось следующее.

Код:
#Include <Array.au3>
#Include <File.au3>
#Include <7Zip.au3>

Opt('MustDeclareVars', 1)

Global $Path = FileSelectFolder('Select Folder', @ScriptDir)

If Not $Path Then
	Exit
EndIf

Global $FileList = _FileListToArray($Path, '*.zip', 1)

If Not IsArray($FileList) Then
	MsgBox(16, 'Error', 'Not found .zip file(s).')
	Exit
EndIf

Global $Timer = TimerInit()
Global $Index, $Percent, $Count = 0, $Prev = 0, $Cache = _CacheRead($Path & '\cache.dat')

Dim $Data[500][4] = [[0]]

ProgressOn('Scaning...', 'Files: 0', '0 %')

For $i = 1 To $FileList[0]
	$Data[0][0] += 1
	If $Data[0][0] > UBound($Data) - 1 Then
		ReDim $Data[$Data[0][0] + 500][4]
	EndIf
	$Data[$Data[0][0]][0] = $FileList[$i]
	$Data[$Data[0][0]][1] = FileGetTime($Path & '\' & $FileList[$i], 0, 1)
	$Data[$Data[0][0]][2] = FileGetSize($Path & '\' & $FileList[$i])
	$Index = _CacheGetIndex($Path & '\cache.dat', $FileList[$i], $Cache)
	If ($Index) And ($Data[$Data[0][0]][1] = $Cache[$Index][1]) And ($Data[$Data[0][0]][2] = $Cache[$Index][2]) Then
		$Data[$Data[0][0]][3] = $Cache[$Index][3]
	Else
		$Data[$Data[0][0]][3] = _7ZipGetFileCount($Path & '\' & $FileList[$i])
	EndIf
	$Count += $Data[$Data[0][0]][3]
	$Percent = Round($i / $FileList[0] * 20)
	If $Percent <> $Prev Then
		ProgressSet(5 * $Percent, (5 * $Percent) & ' %', 'Files: ' & $Count)
		$Prev = $Percent
	EndIf
Next

ProgressOff()

ReDim $Data[$Data[0][0] + 1][4]

_CacheWrite($Path & '\cache.dat', $Data)

MsgBox(0, 'Result', 'Files: ' & $Count & @CR & StringFormat('Elapsed: %.2f sec', TimerDiff($Timer) / 1000) & @CR)

Func _CacheGetIndex($sFile, $sName, ByRef $aData)
	If IsArray($aData) Then
		For $i = 1 To $aData[0][0]
			If $aData[$i][4] Then
				If $aData[$i][0] = $sName Then
					$aData[$i][4] = 0
					Return $i
				EndIf
			EndIf
		Next
	EndIf
	Return 0
EndFunc   ;==>_CacheGetIndex

Func _CacheRead($sFile)

	Local $Data, $Item, $Cache = 0

	_FileReadToArray($sFile, $Data)
	If Not @error Then
		Dim $Cache[$Data[0] + 1][5] = [[0]]
		For $i = 1 To $Data[0]
			$Item = StringSplit($Data[$i], '|')
			If $Item[0] = 4 Then
				$Cache[0][0] += 1
				$Cache[$Cache[0][0]][0] = $Item[1]
				$Cache[$Cache[0][0]][1] = $Item[2]
				$Cache[$Cache[0][0]][2] = Number($Item[3])
				$Cache[$Cache[0][0]][3] = Number($Item[4])
				$Cache[$Cache[0][0]][4] = 1
			EndIf
		Next
		If $Cache[0][0] Then
			ReDim $Cache[$Cache[0][0] + 1][5]
		Else
			$Cache = 0
		EndIf
	EndIf
	Return $Cache
EndFunc   ;==>_CacheRead

Func _CacheWrite($sFile, ByRef $aData)

	Local $Str = ''

	For $i = 1 To $aData[0][0]
		$Str &= $aData[$i][0] & '|' & $aData[$i][1] & '|' & $aData[$i][2] & '|' & $aData[$i][3] & @CRLF
	Next
	If Not FileWrite($sFile, StringTrimRight($Str, 2)) Then
		FileDelete($sFile)
		Return 0
	EndIf
	Return 1
EndFunc   ;==>_CacheWrite


Тестировал на 3000 .zip файлов, Windows 7 x86, AutoIt 3.3.6.1

Первое сканирование ~23.32 сек. (видимо сильно зависит от содержания архивов, у меня были простенькие архивы многократно продублированные)
Последующие ~4.84 сек.

При каждом сканировании создается кэш (cache.dat) в той же папке, где и сканируемые .zip файлы. Далее происходит поиск в этом кэше нужного файла, и, если файл найден и время изменения + размер файла совпадают, то количество файлов в архиве берется из кэша, иначе вызывается функция _7ZipGetFileCount().

Самое интересное, что здесь есть еще что оптимизировать...



Добавлено:
Сообщение автоматически объединено:

kosjachok сказал(а):
Во всем этом смущает - поиск по массиву... Где - то слышал что эта операция тоже замедляет скрипт...

В данном случае больше времени отнимает не поиск в массиве, а непосредственно сравнение строк... Можно сделать следующим образом. Ищем имя файла в массиве. Как нашли, обнуляем эту ячейку и при следующих проходах просто пропускаем такие "нулевые" ячейки. Такой подход уменьшает время поиска в несколько раз.

Код:
For $i = 0 To UBound($aArray) - 1
	If $aArray[$i] Then
		If $aArray[$i] = $sFile Then
			...
			$aArray[$i] = 0
		EndIf
	EndIf
Next
 
Автор
K

kosjachok

Новичок
Сообщения
30
Репутация
3
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Yashied сказал(а):
У меня получилось следующее.
Что я могу сказать...
Первое сканирование - 7 минут - отлично!
34174582.png

Второе сканирование - 21 сек - превосходно!
79026647.png

Результат превзошел все мои ожидания!

Думаю вопрос можно считать решённым!
Интересно что можно ещё допилить напильником, чтобы ускорить процесс?
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Код:
#Include <Array.au3>
#Include <File.au3>
#Include <7Zip.au3>

Opt('MustDeclareVars', 1)

Global $Path = FileSelectFolder('Select Folder', @ScriptDir)

If Not $Path Then
	Exit
EndIf

Global $FileList = _FileListToArray($Path, '*.zip', 1)

If Not IsArray($FileList) Then
	MsgBox(16, 'Error', 'Not found .zip file(s).')
	Exit
EndIf

Global $Timer = TimerInit()
Global $Number, $Percent, $Count = 0, $Prev = 0, $Cache = _CacheRead($Path & '\cache.dat')

Dim $Data[500][4] = [[0]]

ProgressOn('Scaning...', 'Files: 0', '0 %')

For $i = 1 To $FileList[0]
	$Data[0][0] += 1
	If $Data[0][0] > UBound($Data) - 1 Then
		ReDim $Data[$Data[0][0] + 500][4]
	EndIf
	$Data[$Data[0][0]][0] = $FileList[$i]
	$Data[$Data[0][0]][1] = FileGetTime($Path & '\' & $FileList[$i], 0, 1)
	$Data[$Data[0][0]][2] = FileGetSize($Path & '\' & $FileList[$i])
	$Number = _CacheGetCount($Data[$Data[0][0]][0], $Data[$Data[0][0]][1], $Data[$Data[0][0]][2], $Cache)
	If $Number Then
		$Data[$Data[0][0]][3] = $Number
	Else
		$Data[$Data[0][0]][3] = _7ZipGetFileCount($Path & '\' & $FileList[$i])
	EndIf
	$Count += $Data[$Data[0][0]][3]
	$Percent = Round($i / $FileList[0] * 20)
	If $Percent <> $Prev Then
		ProgressSet(5 * $Percent, (5 * $Percent) & ' %', 'Files: ' & $Count)
		$Prev = $Percent
	EndIf
Next

ProgressOff()

ReDim $Data[$Data[0][0] + 1][4]

_CacheWrite($Path & '\cache.dat', $Data)

MsgBox(0, 'Result', 'Files: ' & $Count & @CR & StringFormat('Elapsed: %.2f sec', TimerDiff($Timer) / 1000) & @CR)

Func _CacheGetCount($sName, $iTime, $iSize, ByRef $aData)
	If IsArray($aData) Then
		For $i = 1 To $aData[0][0]
			If $aData[$i][0] Then
				If ($aData[$i][2] = $iSize) And ($aData[$i][1] = $iTime) And ($aData[$i][0] = $sName) Then
					$aData[$i][0] = 0
					Return $aData[$i][3]
				EndIf
			EndIf
		Next
	EndIf
	Return 0
EndFunc   ;==>_CacheGetCount

Func _CacheRead($sFile)

	Local $Data, $Item, $Cache = 0

	_FileReadToArray($sFile, $Data)
	If Not @error Then
		Dim $Cache[$Data[0] + 1][4] = [[0]]
		For $i = 1 To $Data[0]
			$Item = StringSplit($Data[$i], '|')
			If $Item[0] = 4 Then
				$Cache[0][0] += 1
				$Cache[$Cache[0][0]][0] = $Item[1]
				$Cache[$Cache[0][0]][1] = $Item[2]
				$Cache[$Cache[0][0]][2] = Number($Item[3])
				$Cache[$Cache[0][0]][3] = Number($Item[4])
			EndIf
		Next
		If Not $Cache[0][0] Then
			$Cache = 0
		EndIf
	EndIf
	Return $Cache
EndFunc   ;==>_CacheRead

Func _CacheWrite($sFile, ByRef $aData)

	Local $Str = ''

	For $i = 1 To $aData[0][0]
		$Str &= $aData[$i][0] & '|' & $aData[$i][1] & '|' & $aData[$i][2] & '|' & $aData[$i][3] & @CRLF
	Next
	If Not FileWrite($sFile, StringTrimRight($Str, 2)) Then
		FileDelete($sFile)
		Return 0
	EndIf
	Return 1
EndFunc   ;==>_CacheWrite


Одно дополнение, _7ZipGetFileCount() возвращает количество файлов + папок в архиве, а не только количество файлов.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,666
Репутация
2,463
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Yashied [?]
Самое интересное, что здесь есть еще что оптимизировать...
И всё таки я бы не стал использовать _FileListToArray, она очень медленная по сравнению с функцией которая использует командную строку.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,716
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

CreatoR сказал(а):
...она очень медленная по сравнению с функцией которая использует командную строку.

:blink:

Это нонсенс. Вот простой тест:

Код:
#Include <File.au3>

$Timer = TimerInit()
$FileList = _FilesList(@SystemDir, '*.*')
ConsoleWrite(TimerDiff($Timer) & @CR)

$Timer = TimerInit()
$FileList = _FileListToArray(@SystemDir, '*.*', 1)
ConsoleWrite(TimerDiff($Timer) & @CR)

Func _FilesList($sPath, $sFileMask)
    Local $sOut, $aOut, $hDir

    $sOut = StringToBinary("0" & @CRLF, 2)
    $hDir = Run(@ComSpec & ' /U /C DIR "' & $sPath & '\' & $sFileMask & '" /S /B /A-D', @SystemDir, @SW_HIDE, 6)

    While 1
        $sOut &= StdoutRead($hDir, False, True)
        If @error Then ExitLoop
    WEnd

    $aOut = StringRegExp(BinaryToString($sOut, 2), "[^\r\n]+", 3)
    If @error Then Return SetError(1)

    $aOut[0] = UBound($aOut) - 1
    Return $aOut
EndFunc


_FilesList() медленнее в ~30 раз!

Кроме того, в данном случае речь идет о секундах и десятках секунд, и выигрыш в несколько миллисекунд, это несерьезно. Так что оптимизировать _FileListToArray() нет никакого смысла.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,666
Репутация
2,463
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

Yashied [?]
Вот простой тест
:smile: _FilesList возвращает список учитывая подкаталоги, отсюда и замедление, но даже без «\S» (учитвать подкаталоги) она немного медленнее.

Для обычного поиска, да согласен, _FileListToArray будет быстрее, но для рекурсии использовать встроенные средства не стоит.
 

Freesty1er

Новичок
Сообщения
7
Репутация
0
А как сделать такую же программу для RAR архивов?
 

dwerf

Использует ArchLinux
Сообщения
478
Репутация
219
Freesty1er сказал(а):
А как сделать такую же программу для RAR архивов?


Можно как-то так, через unrar.exe
Код:
#include <Constants.au3>

Local $sUnrar = @ScriptDir & '\unrar.exe'
Local $sArchive = @ScriptDir & '\a.rar'

Local $iPid = Run('"' & $sUnrar & '" lb "' & $sArchive & '"', @ScriptDir, @SW_HIDE, $STDERR_CHILD + $STDOUT_CHILD)
Local $sText
While 1
    $sText &= StdoutRead($iPid)
    If @error Then ExitLoop
Wend
While 1
	$sText &= StderrRead($iPid)
	If @error Then ExitLoop
Wend

$aLines = StringSplit(StringStripCR($sText), @LF)
MsgBox(0, '', $aLines[0] & ' files' & @CRLF & @CRLF & $sText)
 
Верх