Что нового

[Файловая система] Оптимальный алгоритм подсчета количества файлов в 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 711
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

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

CreatoR

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

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

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


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

dwerf

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

CreatoR [?]
Если в папке только нужные архивы, то можно использовать DirGetSize:
Это будет колличество архивов в папке, а не колличество файлов в архивах.
Или я чего то не уловил?
 

CreatoR

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

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

kosjachok

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

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

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

Yashied

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

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


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

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

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8 471
Репутация
2 401
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 711
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

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

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

kosjachok

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

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

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

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
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 минут - отлично!

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

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

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

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
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 471
Репутация
2 401
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

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

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5 379
Репутация
2 711
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 471
Репутация
2 401
Re: Оптимальный алгоритм подсчета количества файлов в 3000-ах архивах...

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

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

Freesty1er

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

dwerf

Использует ArchLinux
Сообщения
478
Репутация
218
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)
 
Верх