【问题标题】:Excel VBA efficient get file names functionExcel VBA高效获取文件名功能
【发布时间】:2014-12-03 11:18:34
【问题描述】:

我需要在 excel 2010 中使用 VBA 从远程服务器上的文件夹中获取文件名集合。我有一个可以工作的功能,在大多数情况下它可以完成这项工作,但是远程服务器经常很糟糕,可怕的网络性能问题。这意味着循环遍历 300 个文件以将其名称放入一个集合可能需要 10 分钟,文件夹中的文件数量可能会增长到数千个,因此这是行不通的,我需要一种获取所有文件名的方法在单个网络请求中而不是循环。我相信它连接到需要时间的远程服务器,因此单个请求应该能够相当快地一次性获取所有文件。

这是我目前拥有的功能:

Private Function GetFileNames(sPath As String) As Collection
'takes a path and returns a collection of the file names in the folder

Dim oFolder     As Object
Dim oFile       As Object
Dim oFSO        As Object
Dim colList     As New Collection

Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFolder = oFSO.GetFolder(folderpath:=sPath)

For Each oFile In oFolder.Files
    colList.Add oFile.Name
Next oFile

Set GetFileNames = colList

Set oFolder = Nothing
Set oFSO = Nothing

End Function

【问题讨论】:

  • + 1 好问题 :) 你差点让我思考!

标签: vba excel optimization filesystemobject


【解决方案1】:

这个速度快如闪电:

  Sub filesTest()
    Dim x() As String
    x = Function_FileList("YOUR_PATH_AND_FOLDER_NAME")
    Debug.Print Join(x, vbCrLf)
  End Sub

调用这个函数:

 Function Function_FileList(FolderLocation As String)
    Function_FileList = Filter(Split(CreateObject("wscript.shell").exec("cmd /c Dir """ & FolderLocation & """ /b /a-d").stdout.readall, vbCrLf), ".")
 End Function

【讨论】:

  • + 1 简直太美了!
  • 如果您的网络连接速度较慢或文件很多,也不会更快。 dir 在内部进行迭代,通过exec 运行它意味着它在您的本地计算机上运行并遭受相同的网络延迟。
  • @KenWhite 如果将上面的代码放在一个批处理文件中,然后将该文件复制到远程文件夹,然后从那里运行呢?
  • @SiddharthRout:除非您使用rexec 或类似的东西,否则它仍将在本地计算机上运行并通过网络检索列表。然而,问题不在于在远程机器上运行一个进程然后传输一个文件。它是关于在不迭代的情况下检索目录列表,而这根本是不可能的。 (Raymond Chen 写了一系列关于这个问题的文章,与 Explorer 通过慢速网络连接检索文件列表有关,但我手边没有指向它们的链接。)
  • @KenWhite:嗯,如果不通过网络检索列表而是将远程文件夹中的列表输出到文本文件然后检索该文本文件怎么办?
【解决方案2】:

好的,我找到了适合我情况的解决方案,也许其他人也会觉得它有用。此解决方案使用 Windows API 并在 1 秒或更短的时间内为我获取文件名,而 FSO 方法需要几分钟。它仍然涉及一个循环,所以我不确定为什么它会这么快,但确实如此。

这采用类似“c:\windows\”的路径并返回该文件夹中所有文件(和目录)的集合。我使用的确切参数需要 Windows 7 或更高版本,请参阅声明中的 cmets。

'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 GetFiles(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 GetFiles = colFiles

End Function

【讨论】:

    【解决方案3】:

    我认为会有一个 API 可以让我在不循环的情况下获取目录中的文件名,但找不到它。我知道的所有代码都涉及使用fsodir 循环。

    那么是否可以在不循环的情况下获取文件名。我想是的...这是我能想到的一种方法...

    当您在 DOS 提示符下键入以下命令时,整个文件结构将发送到一个文本文件中

    Dir C:\Temp\*.* > C:\Temp\MyFile.Txt
    

    从 VBA 执行上述操作

    Sub Sample()
        Dim sPath As String
    
        sPath = "C:\Temp\"
    
        '~~> DIR C:\Temp\*.* > C:\Temp\MyFile.txt
        retval = Shell("cmd.exe /c Dir " & sPath & "*.* > " & sPath & "MyFile.Txt")
    End Sub
    

    例如(这是存储在 MyFile.Txt 中的内容)

    Volume in drive C is XXXXXXX
    Volume Serial Number is XXXXXXXXX
    
    Directory of C:\Temp
    
    10/08/2014  11:28 PM    <DIR>          .
    10/08/2014  11:28 PM    <DIR>          ..
    10/08/2014  11:27 PM               832 aaa.txt
    10/08/2014  11:28 PM                 0 bbb.txt
    10/08/2014  11:26 PM                 0 New Bitmap Image.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_2.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_2_2.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_3.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_3_2.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_4.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_4_2.bmp
    10/08/2014  11:26 PM                 0 New Bitmap Image_5.bmp
                10 File(s)            832 bytes
                 2 Dir(s)  424,786,952,192 bytes free
    

    所以现在您需要做的就是将文本文件从远程文件夹复制到您的文件夹,然后简单地解析它以获取文件名。

    【讨论】:

    • 这仍然从本地计算机运行dir 命令并通过网络请求文件列表。通过cmd.exe 运行它仍然在本地执行它。您必须通过网络复制批处理文件或脚本,使用rexec 或类似的东西远程执行它,然后在远程进程完成后通过网络传输生成的文件(这意味着您必须等待并轮询完成)。
    • 是的,但我想这是 OP 目前唯一的选择?
    • 这不会是一个改进。 :-) 通过rexec 启动文件、轮询、然后传输文本文件(然后解析文本文件以获取文件列表)的开销将对性能产生影响。
    • 我刚刚想到...如果我们提供(该机器的)cmd 的完整路径,这样它就不会从本地机器运行 cmd?
    • 行不通。 cmd 仍将在本地计算机上运行。为了在远程机器上运行它,您必须在远程机器上(在该机器自己的操作系统中)实际执行它,而不是在本地机器上。使用远程路径从本地计算机运行它只会导致整个应用程序 (cmd.exe) 被检索以便在本地操作系统上执行。
    猜你喜欢
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 2013-06-23
    • 1970-01-01
    • 2018-05-11
    • 1970-01-01
    相关资源
    最近更新 更多