我有一个相当大的Outlook PST文件(13 GB)。出于某种索引目的,我需要扫描整个PST文件以获取所有PR_SEARCH_KEY,然后将其写入文本文件。此递归过程用于循环浏览所有文件夹和子文件夹,以从每个电子邮件和非电子邮件项目中获取PR_SEARCH_KEY。完成整个扫描并将其写入文本文件花了将近30分钟。我在这里想念吗?我是Outlook和VB.NET的新手。

Dim dict = New Dictionary(Of String, String)

Sub PrepareIndexing()
    Dim oApp As Outlook.Application
    Dim objName As Outlook.NameSpace
    Dim sFolder As Outlook.Folder        
    oApp = CreateObject("Outlook.Application")
    objName = oApp.GetNamespace("MAPI")
    sFolder = objName.Folders.Item("pstname")

    dict.Clear()

    Call InitIndexing(sFolder)

    For Each d As KeyValuePair(Of String, String) In dict
        Using outputFile As New StreamWriter("E:\" & Convert.ToString("file.txt"), True)
            outputFile.WriteLine(d.Key & "," & d.Value)
        End Using
    Next
    dict.Clear()        
End Sub


Sub InitIndexing(f As Outlook.Folder)
    Dim PropName, skey As String
    Dim oPA As Outlook.PropertyAccessor
    PropName = "http://schemas.microsoft.com/mapi/proptag/0x300B0102" 'Pr_Search_Key

    If f.Folders.Count > 0 Then
        For c = 1 To f.Folders.Count
            Dim Folder As Outlook.Folder = f.Folders.Item(c)
            Dim r As String = Folder.FolderPath
            For Each ml In Folder.Items
                oPA = ml.PropertyAccessor
                skey = oPA.BinaryToString(oPA.GetProperty(PropName))
                If Not dict.ContainsKey(skey) Then
                    dict.Add(skey, r)
                End If
            Next
            Call InitIndexing(Folder)
        Next
    End If
End Sub


评论

我将语言标签从vba更改为vb.net。 VBA没有Using语句,也没有强类型的CreateObject调用...

谢谢,我是一位真正的新手,对vba或vb.net几乎一无所知

首先,我将把您的逻辑分成几部分。创建一个获取所有文件夹的方法,另一个获取解析文件夹的方法...这样,您将知道哪种方法花费最多的时间。是文件夹搜索BinaryToString,最后保存到文件吗?

@ Vogel612 VBA绝对可以将CreateObject的输出强制转换为需要使用的任何类型。之所以只将对象变量声明为Object的原因是,当您使用CreateObject通过其已注册的ProgId创建对象的实例时,通常没有对其定义的类型库的引用。VBA或VB .NET,当您已经可以执行New Outlook.Application时,就没有理由使用CreateObject(“ Outlook.Application”)。

用VB.NET代码做什么?显式的Call语法已经在17年前在VBA中过时了!

#1 楼

我认为这是代码的瓶颈。您正在为字典的每个KeyValuePair打开,写入和关闭文件。
将其更改为
For Each d As KeyValuePair(Of String, String) In dict
    Using outputFile As New StreamWriter("E:\" & Convert.ToString("file.txt"), True)
        outputFile.WriteLine(d.Key & "," & d.Value)
    End Using
Next  

,它应该运行得快得多。
为什么,为什么使用Convert.ToString("file.txt")吗?

InitIndexing()
,通过更改f.Folders.Count的条件来检查Count = 0是否可以从Sub提前返回,因此可以像这样保存一个缩进级别/>,但是您在这里遇到了更大的问题:命名!您不应该使用缩写来命名事物。如果您在3个月后回到此代码,您将不了解oPArobjName的含义。您应该始终使用有意义的名称来命名事物。

评论


\ $ \ begingroup \ $
我已经按照您的建议(所有要点)更改了代码,但仍然花了将近30分钟。我正在考虑是否需要花费大量时间来处理这些问题:1)使用字典来存储数据而不是数组或其他任何东西,2)通过访问Outlook属性获取键(PR_SEARCH_KEY),这可能是唯一的方法。我不知道这两点是否还有更好的方法。请提出建议。谢谢。
\ $ \ endgroup \ $
– Coder_v0.01
17年9月27日在11:35

\ $ \ begingroup \ $
以“发布”模式进行编译并执行已编译的应用程序。应该快得多。
\ $ \ endgroup \ $
– Heslacher
17-09-27在12:44

\ $ \ begingroup \ $
经过尝试,我倾向于认为Outlook互操作性太慢了(与office和excel一样)。
\ $ \ endgroup \ $
– Johnbot
17年9月27日在13:07

\ $ \ begingroup \ $
@Hayat问题不是VB.NET,而是COM API。
\ $ \ endgroup \ $
– Mathieu Guindon♦
17-09-27在23:18

\ $ \ begingroup \ $
我并不震惊,一个13g的文件要花费3000万来处理,我的意思是说,如果我们已经考虑到使用经典硬盘读取文件的速度为30M / s,那么仅读取文件就已经有500万(没有任何内容)否则会打到硬盘驱动器上,您是否在使用防病毒软件?:p)。此外,您也在写作。
\ $ \ endgroup \ $
–沃尔夫特
17年9月28日在13:25

#2 楼

COM互操作本身会降低性能,但是即使通过COM(例如在VBA中)访问它,Outlook类型库/对象模型仍然非常缓慢。如果对性能有要求,我会考虑在不涉及Outlook对象模型的情况下解析PST文件。使用类似PST-Parser的东西(没有从属关系,只是一个我发现用于PST解析器的OSS项目)。

因此,鉴于性能问题的很大一部分主要是由于该方法(Outlook互操作) ,我不会从性能的角度审查您的代码-但在可维护性方面,存在许多问题。


隐式访问修饰符:VB.NET中的成员是Public默认情况下,就像它们在其VB6祖先中一样。这令人困惑,因为在许多其他编程语言中,默认情况下模块成员为Private。无论如何,避免使用隐式访问修饰符;明确指定它们,不留歧义:如果要从模块外部调用它,请将其命名为Public。如果要从模块内部调用它,请使其为Private
隐式ByVal修饰符:除非另有说明,否则VB.NET中的参数将按值传递。这与VB6 / VBA代码形成鲜明对比,在VB6 / VBA代码中,默认情况下通过引用传递参数。如果这些修饰符在任何地方都是显式的,那么经常在VBA和VB.NET代码之间进行上下文切换的读者将可以轻松得多。重要的信息。
过时的语法:17年前,显式的ByVal语法已在VB6 / VBA中过时。没有任何理由将该关键字带入您的VB.NET代码。
变量范围不一致:在VBA中,变量的最小范围是成员级别。在VB.NET中,块也是作用域,这意味着,如果您在过程/成员级别声明局部变量,并且仅在ByRef循环迭代中使用它,则该变量的作用域会超出所需范围,这使得代码难以遵循。例如,Call中的ForoPA实际上属于skey循环中的单个迭代:它们超出了循环范围是没有意义的。声明变量尽可能接近其用法;在过程的顶部没有“声明墙”。

关于此的一个字: >
在VBA中将InitIndexing声明为隐式For,并且将PropName声明为Variant。 VB.NET对此进行了“修复”,因此skeyString都是字符串-但是用一条指令声明多个变量是完全合法的,但是这样做令人困惑且无用,尤其是因为PropName看起来是常量而skey
范围比实际需要的要广泛-我宁愿看到以下内容:

Dim PropName, skey As String


避免单字母变量名称-使用有意义的,可发音的名称。 >


PropName是文件夹-为什么不叫它skey

f看起来像是带有“ s-for-string”系统的匈牙利语的“键”具有绝对零值的前缀。那个键代表什么?看起来它是二进制属性的字符串表示形式。所以,这是一个财产。那么folder呢?

skey确实是propertyKey,或者oPA在这种情况下足够好。该propertyAccessor前缀是系统匈牙利语(“ o-for-object”?放它!反正一切都是物体!)。

accessor绝对没有任何意义。这是一个o,但是您要迭代一个对象集合,并且使用c循环执行此操作比使用folderIndex循环执行此操作要快得多,对于循环数组而言,这样做更快。此外,1不是那么性感吗?

Const PropertyName As String = "http://schemas.microsoft.com/mapi/proptag/0x300B0102"


For Each还是一些看似随机挑选的单字母名称,没有任何含义。它代表For ...因此,r似乎是一个更好的名称。所以应该是Folder.FolderPath,但这似乎是多余的分配-path仅使用一次,并有条件地使用;因此,您无条件访问一个昂贵的COM互操作对象以分配一个值,该值仅在字典中尚不存在该键的情况下才会使用-我将该变量移到它所属的条件范围内。 br /> Dim path As String = subFolder.FolderPath让我想知道应保留2个字母的名称是怎么做的。您要遍历文件夹项,因此rml似乎是一个更好用的名称。而不是填充超出其范围的内容。

但是,当它被迭代时,它被用作item-您在此处使用字典,child和每个项目的dict都可以当场很好地串联在一起。项目返回到客户端循环并在迭代过程中发出结果:

For Each subFolder In folder.Folder


我不清楚条件有可能评估的确切方式到InitIndexing(即,如何复制密钥?)。

使用GetAllFolderItems,您可以ed处理递归结果的方式有所不同:

Yield Return accessor.BinaryToString(accessor.GetProperty(PropertyName)) & ", " & subFolder.FolderPath



1在VB6 / VBA中是正确的,但是不再.NET。

评论


\ $ \ begingroup \ $
+1“ COM Interop本身就是一种性能出色的产品” .... ooooo ya。
\ $ \ endgroup \ $
–有趣的名称-在这里
17年9月27日在17:52

\ $ \ begingroup \ $
嗯,我知道关于For vs ForEach的观点对于VBA是正确的,但不确定是否适用于VB.NET。
\ $ \ endgroup \ $
– Mathieu Guindon♦
17年9月27日在20:11

\ $ \ begingroup \ $
@ Mat'sMug如果您想谈谈一个人对另一个人的速度,我认为这无关紧要。 ForEach的主要卖点是它消除了语法混乱,因此如果您不需要额外的枚举器,使用它几乎总是更好。
\ $ \ endgroup \ $
– BgrWorker
17年9月28日在9:48

\ $ \ begingroup \ $
@BgrWorker我就是这么想的。在VBA中,使用for循环迭代对象集合比使用foreach慢27倍!
\ $ \ endgroup \ $
– Mathieu Guindon♦
17年9月28日在10:36