Performance 优化子目录中递归文件搜索的速度?
我正在寻找使用Excel宏递归搜索文件模式子目录的最快执行方法。Excel VBA在这方面似乎相当缓慢 到目前为止我已经尝试过的事情(一些基于其他stackoverflow建议):Performance 优化子目录中递归文件搜索的速度?,performance,vba,excel,Performance,Vba,Excel,我正在寻找使用Excel宏递归搜索文件模式子目录的最快执行方法。Excel VBA在这方面似乎相当缓慢 到目前为止我已经尝试过的事情(一些基于其他stackoverflow建议): 独占使用Dir在子目录中递归,并在每个文件夹中搜索filepattern。(最慢) 使用Folder.Files集合遍历FileSystemObject文件夹,根据filepattern检查每个文件。(更好,但仍然缓慢) 遍历FileSystemObject文件夹,然后使用Dir检查每个文件夹中的文件模式(到目前为
- 独占使用Dir在子目录中递归,并在每个文件夹中搜索filepattern。(最慢)
- 使用Folder.Files集合遍历FileSystemObject文件夹,根据filepattern检查每个文件。(更好,但仍然缓慢)
- 遍历FileSystemObject文件夹,然后使用Dir检查每个文件夹中的文件模式(到目前为止最快,但每个文件仍需要几秒钟的时间,如果可能的话,我希望进行优化)
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Function Recurse(sPath As String, targetName As String) As String
Dim FSO As New FileSystemObject
Dim myFolder As Folder
Dim mySubFolder As Folder
Dim myFile As File
On Error Resume Next
Set myFolder = FSO.GetFolder(sPath)
If Err.Number <> 0 Then
MsgBox "Error accessing " & sPath & ". The macro will abort."
Err.Clear
Exit Function
End If
On Error GoTo 0
Dim foundFolderPath As String
Dim foundFileName As String
foundFolderPath = ""
foundFileName = ""
For Each mySubFolder In myFolder.SubFolders
foundFileName = Dir(mySubFolder.Path & "\" & targetName & "*")
If foundFileName <> vbNullString Then
foundFolderPath = mySubFolder.Path & "\" & foundFileName
End If
If foundFolderPath <> vbNullString Then
Recurse = foundFolderPath
Exit Function
End If
foundFolderPath = Recurse(mySubFolder.Path, targetName)
If foundFolderPath <> vbNullString Then
Recurse = foundFolderPath
Exit Function
End If
Next
End Function
Sub FindFile()
Dim start As Long
start = GetTickCount()
Dim targetName As String
Dim targetPath As String
targetName = Range("A1").Value 'Target file name without extension
targetPath = "C:\Example\" & Range("B1").Value 'Subfolder name
Dim target As String
target = Recurse(targetPath, targetName)
Dim finish As Long
finish = GetTickCount()
MsgBox "found: " & target & vbNewLine & vbNewLine & (finish - start) & " milliseconds"
End Sub
选项显式
私有声明函数GetTickCount Lib“kernel32”()的长度为
函数递归(sPath作为字符串,targetName作为字符串)作为字符串
将FSO设置为新的FileSystemObject
将myFolder设置为文件夹
将mySubFolder设置为文件夹
将myFile设置为文件
出错时继续下一步
设置myFolder=FSO.GetFolder(sPath)
如果错误号为0,则
MsgBox“访问时出错”&sPath&>。宏将中止
呃,明白了
退出功能
如果结束
错误转到0
将foundFolderPath设置为字符串
将文件名设置为字符串
foundFolderPath=“”
foundFileName=“”
对于myFolder.SubFolders中的每个mySubFolder
foundFileName=Dir(mySubFolder.Path&“\”&targetName&“*”)
如果foundFileName vbNullString,则
foundFolderPath=mySubFolder.Path&“\”和foundFileName
如果结束
如果是foundFolderPath vbNullString,则
Recurse=foundFolderPath
退出功能
如果结束
foundFolderPath=Recurse(mySubFolder.Path,targetName)
如果是foundFolderPath vbNullString,则
Recurse=foundFolderPath
退出功能
如果结束
下一个
端函数
子FindFile()
开始的时间越长越好
start=GetTickCount()
将targetName设置为字符串
将targetPath设置为字符串
targetName=Range(“A1”).Value“无扩展名的目标文件名”
targetPath=“C:\Example\”&Range(“B1”).Value”子文件夹名称
将目标变暗为字符串
目标=递归(targetPath,targetName)
黯淡的余味
finish=GetTickCount()
MsgBox“找到:”&target&vbNewLine&vbNewLine&(finish-start)&“毫秒”
端接头
根据已接受的答案更新文件搜索功能
这个版本的FindFile()执行速度大约是我在上面问题中最初粘贴的方法的两倍。正如在下面的文章中所讨论的,这应该适用于32位或64位版本的Excel 2010及更新版本
Option Explicit
Private Declare PtrSafe Function FindClose Lib "kernel32" (ByVal hFindFile As LongPtr) As Long
Private Declare PtrSafe Function FindFirstFileW Lib "kernel32" (ByVal lpFileName As LongPtr, ByVal lpFindFileData As LongPtr) As LongPtr
Private Declare PtrSafe Function FindNextFileW Lib "kernel32" (ByVal hFindFile As LongPtr, ByVal lpFindFileData As LongPtr) As LongPtr
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Const MAX_PATH As Long = 260
Const ALTERNATE As Long = 14
' Can be used with either W or A functions
' Pass VarPtr(wfd) to W or simply wfd to A
Private Type WIN32_FIND_DATA
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * MAX_PATH
cAlternate As String * ALTERNATE
End Type
Private Const FILE_ATTRIBUTE_DIRECTORY As Long = 16 '0x10
Private Const INVALID_HANDLE_VALUE As LongPtr = -1
Function Recurse(folderPath As String, fileName As String)
Dim fileHandle As LongPtr
Dim searchPattern As String
Dim foundPath As String
Dim foundItem As String
Dim fileData As WIN32_FIND_DATA
searchPattern = folderPath & "\*"
foundPath = vbNullString
fileHandle = FindFirstFileW(StrPtr(searchPattern), VarPtr(fileData))
If fileHandle <> INVALID_HANDLE_VALUE Then
Do
foundItem = Left$(fileData.cFileName, InStr(fileData.cFileName, vbNullChar) - 1)
If foundItem = "." Or foundItem = ".." Then 'Skip metadirectories
'Found Directory
ElseIf fileData.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY Then
foundPath = Recurse(folderPath & "\" & foundItem, fileName)
'Found File
'ElseIf StrComp(foundItem, fileName, vbTextCompare) = 0 Then 'these seem about equal
ElseIf InStr(1, foundItem, fileName, vbTextCompare) > 0 Then 'for performance
foundPath = folderPath & "\" & foundItem
End If
If foundPath <> vbNullString Then
Recurse = foundPath
Exit Function
End If
Loop While FindNextFileW(fileHandle, VarPtr(fileData))
End If
'No Match Found
Recurse = vbNullString
End Function
Sub FindFile()
Dim targetName As String
Dim targetPath As String
targetName = Range("A4").Value
targetPath = "C:\Example\" & Range("B4").Value
Dim target As String
target = Recurse(targetPath, targetName)
MsgBox "found: " & target
End Sub
选项显式
私有声明PtrSafe函数FindClose Lib“kernel32”(ByVal hFindFile作为LongPtr)为Long
私有将PtrSafe函数FindFirstFileW Lib“kernel32”(ByVal lpFileName作为LongPtr,ByVal lpFindFileData作为LongPtr)声明为LongPtr
私有将PtrSafe函数FindNextFileW Lib“kernel32”(ByVal hFindFile作为LongPtr,ByVal lpFindFileData作为LongPtr)声明为LongPtr
私有类型文件时间
dwLowDateTime尽可能长
dwHighDateTime尽可能长
端型
常量最大路径长度=260
常量替换长度=14
'可与W或A函数一起使用
'将VarPtr(wfd)传递给W或将wfd传递给A
私有类型WIN32\u查找\u数据
dwFileAttributes尽可能长
ftCreationTime作为文件时间
ftLastAccessTime作为文件时间
ftLastWriteTime作为文件时间
nFileSizeHigh尽可能长
只要
DW保留的长度为
D保留的时间尽可能长
cFileName作为字符串*MAX\u路径
cAlternate作为字符串*ALTERNATE
端型
Private Const FILE_ATTRIBUTE_目录长度=16'0x10
Private Const无效的\u句柄\u值为LongPtr=-1
函数递归(folderPath为字符串,文件名为字符串)
将文件句柄设置为LongPtr
将模式设置为字符串
将路径设置为字符串
将foundItem设置为字符串
将文件数据设置为WIN32_FIND_数据
searchPattern=folderPath&“\*”
foundPath=vbNullString
fileHandle=FindFirstFileW(StrPtr(searchPattern),VarPtr(fileData))
如果fileHandle\u HANDLE\u值无效,则
做
foundItem=Left$(fileData.cFileName,InStr(fileData.cFileName,vbNullChar)-1)
如果foundItem=“”或foundItem=“…”则“跳过元目录”
'找到目录
ElseIf fileData.dwFileAttributes和文件属性目录
foundPath=Recurse(folderPath&“\”&foundItem,文件名)
'找到文件
'ElseIf StrComp(foundItem、fileName、vbTextCompare)=0然后'
ElseIf InStr(1,foundItem,fileName,vbTextCompare)>0,然后为性能设置'
foundPath=folderPath&“\”&foundItem
如果结束
如果foundPath vbNullString,则
Recurse=foundPath
退出功能
如果结束
FindNextFileW(fileHandle,VarPtr(fileData))时循环
如果结束
“没有找到匹配项
Recurse=vbNullString
端函数
子FindFile()
将targetName设置为字符串
将targetPath设置为字符串
targetName=范围(“A4”).值
targetPath=“C:\Example\”和范围(“B4”).值
将目标变暗为字符串
目标=递归(targetPath,targetName)
MsgBox“找到:”&目标
端接头
使用FindFirstFile或FindFirstFileEx。内置的本机API将比VBA执行得快得多
a
Option Explicit
Private Declare PtrSafe Function FindClose Lib "kernel32" (ByVal hFindFile As LongPtr) As Long
Private Declare PtrSafe Function FindFirstFileW Lib "kernel32" (ByVal lpFileName As LongPtr, ByVal lpFindFileData As LongPtr) As LongPtr
Private Declare PtrSafe Function FindNextFileW Lib "kernel32" (ByVal hFindFile As LongPtr, ByVal lpFindFileData As LongPtr) As LongPtr
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Const MAX_PATH As Long = 260
Const ALTERNATE As Long = 14
' Can be used with either W or A functions
' Pass VarPtr(wfd) to W or simply wfd to A
Private Type WIN32_FIND_DATA
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * MAX_PATH
cAlternate As String * ALTERNATE
End Type
Private Const INVALID_HANDLE_VALUE As LongPtr = -1
Private Sub Form_Load()
Dim hFile As LongPtr
Dim sFileName As String
Dim wfd As WIN32_FIND_DATA
sFileName = "c:\*.*" ' Can be up to 32,767 chars
hFile = FindFirstFileW(StrPtr(sFileName), VarPtr(wfd))
If hFile <> INVALID_HANDLE_VALUE Then
Do While FindNextFileW(hFile, VarPtr(wfd))
Debug.Print Left$(wfd.cFileName, InStr(wfd.cFileName, vbNullChar) - 1)
Loop
FindClose hFile
End If
End Sub
'for windows API call to FindFirstFileEx
Private Const INVALID_HANDLE_VALUE = -1
Private Const MAX_PATH = 260
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Type WIN32_FIND_DATA
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * MAX_PATH
cAlternate As String * 14
End Type
Private Const FIND_FIRST_EX_CASE_SENSITIVE As Long = 1
'MSDN: "This value is not supported until Windows Server 2008 R2 and Windows 7."
Private Const FIND_FIRST_EX_LARGE_FETCH As Long = 2
Private Enum FINDEX_SEARCH_OPS
FindExSearchNameMatch
FindExSearchLimitToDirectories
FindExSearchLimitToDevices
End Enum
Private Enum FINDEX_INFO_LEVELS
FindExInfoStandard
FindExInfoBasic 'MSDN: "This value is not supported until Windows Server 2008 R2 and Windows 7."
FindExInfoMaxInfoLevel
End Enum
Private Declare Function FindFirstFileEx Lib "kernel32" Alias "FindFirstFileExA" ( _
ByVal lpFileName As String, ByVal fInfoLevelId As Long, lpFindFileData As WIN32_FIND_DATA, _
ByVal fSearchOp As Long, ByVal lpSearchFilter As Long, ByVal dwAdditionalFlags As Long) As Long
Private Declare Function FindNextFile Lib "kernel32" Alias "FindNextFileA" ( _
ByVal hFindFile As Long, lpFindFileData As WIN32_FIND_DATA) As Long
Private Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long
Private Function GetFileNames(ByVal sPath As String) As Collection
Dim fileInfo As WIN32_FIND_DATA 'buffer for file info
Dim hFile As Long 'file handle
Dim colFiles As New Collection
sPath = sPath & "*.*"
hFile = FindFirstFileEx(sPath & vbNullChar, FindExInfoBasic, fileInfo, FindExSearchNameMatch, 0&, FIND_FIRST_EX_LARGE_FETCH)
If hFile <> INVALID_HANDLE_VALUE Then
Do While FindNextFile(hFile, fileInfo)
colFiles.Add Left(fileInfo.cFileName, InStr(fileInfo.cFileName, vbNullChar) - 1)
Loop
FindClose hFile
End If
Set GetFileNames = colFiles
End Function
Function FindFast(TargetFolder As String, Patt As String)
Dim Folder As Object, SubFolder As Object, File As Object
Dim FQueue As New Collection
'Test view all folders:
' Dim FolderColl As New Collection
Dim Count As Integer
Dim fl As String
With CreateObject("Scripting.FileSystemObject")
FQueue.Add .GetFolder(TargetFolder)
Do While FQueue.Count > 0
Set Folder = FQueue(1)
FQueue.Remove 1
'Code for individual folder
For Each SubFolder In Folder.subFolders
'Test view all folders:
FolderColl.Add SubFolder
'Only 10 folders deep
' Count = Len(SubFolder) - Len(Replace(SubFolder, "\", ""))
' If Count < 13 Then
FQueue.Add SubFolder
' ' Only look for the file in Working folder
' If InStr(1, SubFolder, "Working") > 1 Then
fl = Dir(SubFolder & "\" & Patt)
' Added as exact match return. Otherwise will find all with pattern match
If fl <> "" Then
FindFast = SubFolder & "\" & fl
Exit Function
End If
' End If
' End If
Next SubFolder
Loop
' Test view all folders:
' Dim i As Long
'For i = 1 To FolderColl.Count
' Range("A" & i).value = FolderColl(i)
'Next i
End With
FindFast = vbNullString
End Function
Sub FindFile()
Dim StartTime As Double
Dim SecondsElapsed As Double
Dim target As String
Dim targetName As String
Dim targetPath As String
targetName = "5-3-21_Order_Sent.xlsx"
' Patt = "*_Order_Sent.xlsx"
' or wild extension Patt = "*_ThisName.*"
targetPath = "\\Fulfill\Company\Orders\Completed"
StartTime = Timer
target = FindFast(targetPath, targetName)
Debug.Print target
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print "FindFast: " & SecondsElapsed & " Secs"
MsgBox "found FindFast: " & target & " - " & SecondsElapsed & " Secs"
StartTime = Timer
target = Recurse(targetPath, targetName)
Debug.Print target
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print "Recurse: " & SecondsElapsed & " Secs"
MsgBox "found Recurse: " & target & " - " & SecondsElapsed & " Secs"
End Sub