【问题标题】:Comparison of Dictionary, Collections and Arrays字典、集合和数组的比较
【发布时间】:2015-12-05 10:33:22
【问题描述】:

我正在尝试找出字典与集合和数组相比的相对优势和特性。

我找到了一篇很棒的文章 here,但找不到一个简单的表格来比较所有不同的功能。

有人知道吗?

【问题讨论】:

    标签: excel vba ms-access


    【解决方案1】:

    请参阅下表对集合和字典进行有用的比较。

    (该表总结了this page 直到“早期和晚期绑定”部分。仅供参考,该页面还包含有关使用字典的更多详细信息)

    总的来说,通常最好使用字典或数组。

    在考虑使用集合时,如果大小不变或很少改变,则使用数组可能更合适。在这种情况下,数组可能比集合更有效,因为数组可以非常高效地一次填充和检索所有项目(例如,范围到数组和数组返回到范围)。

    另请注意:

    与数组相比,集合在添加和插入项目以及通过它们的键访问和删除它们方面提供了良好的性能。但是,如果要通过索引访问项目,则性能很差。有关高效执行此操作的信息,请参阅here,其中还讨论了这些列表对象的内部工作原理。

    This cpearson page 有非常有用的代码来处理字典、集合和数组(对它们进行排序,并将它们相互转换!)

    来自 cpearson 页面的一些文本:

    Collection 对象和 Dictionary 对象对于 存储相关数据组。在其他条件相同的情况下,我使用 Dictionary 对象而不是 Collection 对象,因为您有 访问(读取、写入、更改)与一个关联的 Key 属性 字典中的项目。在一个相当糟糕的对象设计中, 集合中的项目是只写的。您可以将密钥分配给项目 当您将项目添加到集合中,但您无法检索 与项目关联的密钥,您也不能(直接)确定是否 密钥存在于集合中。字典非常友好和开放 用他们的钥匙。字典也比 收藏。

    为什么数组可能是一个糟糕的选择。 数组在调整大小和在中间插入项目时要慢得多,因为每个 Redim 将整个内存块复制到更大的位置,如果使用 Preserve,所有值也会复制过来。这可能会转化为每个操作的感知速度慢 - 在潜在的应用程序中)

    VBA 中的集合与字典

    Feature                 | COLLECTION | DICTIONARY | Remark
    ------------------------+------------+------------+--------------------------------
    Usually faster          |            |     X      | 
    ------------------------+------------+------------+--------------------------------
    Supported by VB Script  |            |     X      | Collections do not exist in VBS.
    ------------------------+------------+------------+--------------------------------
                            |            |            | Dicts: Add ref to Miscrosoft 
    Native to VBA           |     X      |            | Scripting Library. Usage:
                            |            |            | Dim MyDict As Scripting.Dictionary
                            |            |            | Set MyDict = New Scripting.Dictionary
    ------------------------+------------+------------+--------------------------------
    Can change Keys and     |            |            | Dict properties are writable.
    Items                   |            |     X      | For collections, remove the item
                            |            |            | and add a new item.
    ------------------------+------------+------------+--------------------------------
                            |            |            | A collection enumerates its items:
                            |            |            |  For Each x In MyCollection
                            |            |            |      Debug.Print x
    Enumerated              |     X      |     X      |  Next x
                            |            |            | A dict enumerates its keys:
                            |            |            |  For Each x In MyDictionary
                            |            |            |      Debug.Print MyDictionary.Item(x)
                            |            |            |  Next x
    ------------------------+------------+------------+--------------------------------
                            |            |            | A 1-d array of keys 
    Directly output to      |            |            | and items can be returned by 
    array                   |            |     X      | dict methods .Keys and .Items.
                            |            |            | (The array is zero-based even 
                            |            |            |  with Option Base 1.)
    ------------------------+------------+------------+--------------------------------
    Retrieve and access     |     X      |     X      |
    items                   |            |            |  
    ------------------------+------------+------------+--------------------------------
    Add items               |     X      |     X      |
    ------------------------+------------+------------+--------------------------------
    Implicitly add items    |            |     X      | Dicts can implicitly add items 
                            |            |            | using .Item property.
    ------------------------+------------+------------+--------------------------------
    Remove items            |     X      |     X      |
    ------------------------+------------+------------+--------------------------------
    Remove all items in     |            |            | With collections, each item must
    one step                |            |     X      | be removed in turn, or the 
                            |            |            | collection destroyed and recreated.
    ------------------------+------------+------------+--------------------------------
    Count items             |     X      |     X      |
    ------------------------+------------+------------+--------------------------------
    Return item using key   |     X      |     X      |
    as lookup value         |            |            |
    ------------------------+------------+------------+--------------------------------
    Return item using       |            |            |
    ordinal position        |     X      |   (Slow)   |
    as lookup value         |            |            |
    ------------------------+------------+------------+--------------------------------
    Return ordinal          |            |            |
    position using item     |     X      |     ??     |
    as lookup value         |            |            |
    ------------------------+------------+------------+--------------------------------
    Retrieve and access     |            |     X      | Collection keys only used to
    keys                    |            |            | look up data, not retrievable.
    ------------------------+------------+------------+--------------------------------
    Keys optional           |     X      |            | Big + of collections, assuming keys
                            |            |            | are not needed. (Access via index.)
    ------------------------+------------+------------+--------------------------------
    Case sensitivity        |            |     X      |
    optional                |            |            |  
    ------------------------+------------+------------+--------------------------------
                            |            |            | Collection keys must be strings.
    Keys can be any type    |            |     X      | Dict keys can have any type
                            |            |            | (except arrays), incl. mixed types.
    ------------------------+------------+------------+--------------------------------
    Keys must be unique     |     X      |     X      |
    ------------------------+------------+------------+--------------------------------
                            |            |            | * For collections, add code:
                            |            |            |  Public Function _
                            |            |            |     Contains(col As Collection, _
    Supports .Exists method |  Remark*   |     X      |     key As Variant) As Boolean
                            |            |            |     On Error Resume Next
                            |            |            |     col(key)
                            |            |            |     Contains = (Err.Number = 0)
    ------------------------+------------+------------+--------------------------------
    Preserve key order when |            |     X      | This is because collection keys 
    sorting by item value   |            |            | are write-only, not read. Poor design!
    

    原图,信息更多,排列更清晰:

    【讨论】:

    • 我最喜欢的 Dicts 功能是我可以将值或键输出为数组。如果这张表不是图片,我会添加该功能,但是唉...
    • @Dick 我已经为你添加了这个。您是否还想添加任何其他内容或发现任何错误? (我已经重组了表格) PS。我尝试重新格式化我的 OneNote 表格以将其作为表格插入到答案中,但无法弄清楚它是如何完成的! (任何想法)
    • 您可能错过了使用 Dictionary 需要设置引用,而 Collection 是 Access 原生的。
    • @Gustav。谢谢。我已经添加了它并改写了上面的文字以将人们指向这一点。
    • 谢谢@Paul。要记住的东西太多了,我依赖于在 Onenote 中创建这样的婴儿床床单(通常使用 snagit 进行屏幕抓取)。我很高兴我现在分享了它。我通常有点沮丧,因为很多文档和帮助没有更简洁和比较。
    【解决方案2】:
    Option Explicit
    
    Sub CollectionsVSdictionaries() ' Requires ref to "Microsoft Scripting Runtime" Library
        Dim c As Collection         ' TypeName 1-based indexed
        Dim d As Dictionary         ' 0-based arrays
        Set c = New Collection      ' or: "Dim c As New Collection"
        Set d = New Dictionary      ' or: "Dim d As New Dictionary"
    
        c.Add Key:="A", Item:="AA": c.Add Key:="B", Item:="BB": c.Add Key:="C", Item:="CC"
        d.Add Key:="A", Item:="AA": d.Add Key:="B", Item:="BB": d.Add Key:="C", Item:="CC"
    
        Debug.Print TypeName(c)    ' -> "Collection"
        Debug.Print TypeName(d)    ' -> "Dictionary"
    
        Debug.Print c(3)            ' -> "CC"
        Debug.Print c("C")          ' -> "CC"
        'Debug.Print c("CC")       ' --- Invalid ---
    
        Debug.Print d("C")          ' -> "CC"
        Debug.Print d("CC")        ' Adds Key:="CC", Item:=""
        Debug.Print d.Items(2)      ' -> "CC"
        Debug.Print d.Keys(2)       ' -> "C"
        Debug.Print d.Keys()(0)     ' -> "A"    - Not well known ***************************
        Debug.Print d.Items()(0)    ' -> "AA"   - Not well known ***************************
    
        'Collection methods:
        '    .Add                   ' c.Add Item, [Key], [Before], [After] (Key is optional)
        '    .Count
        '    .Item(Index)           ' Default property;   "c.Item(Index)" same as "c(Index)"
        '    .Remove(Index)
        'Dictionary methods:
        '    .Add                   ' d.Add Key, Item (Key is required, and must be unique)
        '    .CompareMode           ' 1. BinaryCompare     - case-sensitive   ("A" < "a")
        '    .CompareMode           ' 2. DatabaseCompare   - MS Access only
        '    .CompareMode           ' 3. TextCompare       - case-insensitive ("A" = "a")
        '    .Count
        '    .Exists(Key)           ' Boolean **********************************************
        '    .Item(Key)
        '    .Items                 ' Returns full array: .Items(0)(0)
        '    .Key(Key)
        '    .Keys                  ' Returns full array: .Keys(0)(0)
        '    .Remove(Key)
        '    .RemoveAll             ' ******************************************************
    End Sub
    

    【讨论】:

    • 感谢 Paul - 这是一个有用的资源。
    【解决方案3】:

    关于集合与字典的性能,我发现写入字典的性能与写入集合的性能相似,从字典读取所需的时间大约是从集合读取的两倍。首先创建字典比创建集合要慢。

    这些是我进行 100,000 次迭代读取、写入和创建字典/集合的结果:

    Creating Multiple Dictionaries:   731ms
    Writing To Dictionary:            494ms
    Reading From Dictionary:           65ms
    
    Creating Multiple Collections:     29ms
    Writing To Collection:            459ms
    Reading From Collection:           26ms
    

    请注意,添加对 Microsoft Scripting Runtine 的引用可提高创建多个字典的速度(此处为 495 毫秒)。

    这是我用来测试的代码:

    Option Explicit
    
    Private p_lngTestCount As Long
    
    Sub SetUp()
      p_lngTestCount = 100000
    End Sub
    
    Sub TestAll()
      CreatingMultipleDictionaries
      WritingToDictionary
      ReadingFromDictionary
    
      CreatingMultipleCollections
      WritingToCollection
      ReadingFromCollection
    End Sub
    
    Sub CreatingMultipleDictionaries()
    
      Const sSOURCE As String = "CreatingMultipleDictionaries"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim dcTest As Dictionary
      SetUp
    
      Dim dblTimeElapsed As Double
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
        'Set dcTest = CreateObject("Scripting.Dictionary")
        Set dcTest = New Dictionary
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    
    Sub CreatingMultipleCollections()
    
      Const sSOURCE As String = "CreatingMultipleCollections"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim colTest As Collection
      SetUp
    
      Dim dblTimeElapsed As Double
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
        Set colTest = New Collection
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    
    Sub WritingToDictionary()
    
      Const sSOURCE As String = "WritingToDictionary"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim dcTest
      SetUp
    
      Set dcTest = CreateObject("Scripting.Dictionary")
      'Set dcTest = New Dictionary
    
      Dim dblTimeElapsed As Double
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
       ' Performance about the same for both ways:
        dcTest.Item(CStr(i)) = "test"
        'dcTest.Add CStr(i), "test"
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    
    Sub WritingToCollection()
    
      Const sSOURCE As String = "WritingToCollection"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim colTest As Collection
      SetUp
    
      Dim dblTimeElapsed As Double
      Set colTest = New Collection
    
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
        colTest.Add "test", CStr(i)
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    
    Sub ReadingFromDictionary()
    
      Const sSOURCE As String = "ReadingFromDictionary"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim dcTest
      SetUp
    
      Set dcTest = CreateObject("Scripting.Dictionary")
      'Set dcTest = New Dictionary
      dcTest.Add "key", "test"
    
      Dim stTest As String
      Dim dblTimeElapsed As Double
    
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
        stTest = dcTest.Item("key")
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    
    Sub ReadingFromCollection()
    
      Const sSOURCE As String = "ReadingFromCollection"
    
      Dim oPerfMon As CDevPerformanceMonitor
      Set oPerfMon = New CDevPerformanceMonitor
      Dim i As Long
      Dim colTest As Collection
      SetUp
    
      Dim stTest As String
      Dim dblTimeElapsed As Double
      Set colTest = New Collection
      colTest.Add "test", "key"
    
      oPerfMon.StartCounter
    
      For i = 0 To p_lngTestCount
        stTest = colTest.Item("key")
      Next i
    
      dblTimeElapsed = oPerfMon.TimeElapsed
    
      Debug.Print sSOURCE & ": " & p_lngTestCount & " iterations. " & vbCrLf & _
                  "Time elapsed: " & Round(dblTimeElapsed, 0) & "ms" & vbCrLf
    End Sub
    

    性能监控类(CDevPerformanceMonitor):

    Option Explicit
    
    ' Performance monitoring used in logging
    ' See: https://stackoverflow.com/questions/198409/how-do-you-test-running-time-of-vba-code
    
    Private Type LARGE_INTEGER
      lowpart As Long
      highpart As Long
    End Type
    
    #If VBA7 Then
      Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long
    #Else
      Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long
    #End If
    
    #If VBA7 Then
      Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long
    #Else
      Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long
    #End If
    
    Private m_CounterStart As LARGE_INTEGER
    Private m_CounterEnd As LARGE_INTEGER
    Private m_crFrequency As Double
    
    Private Const TWO_32 = 4294967296#               ' = 256# * 256# * 256# * 256#
    
    Private Function LI2Double(LI As LARGE_INTEGER) As Double
      Dim Low As Double
      Low = LI.lowpart
      If Low < 0 Then
        Low = Low + TWO_32
      End If
      LI2Double = LI.highpart * TWO_32 + Low
    End Function
    
    Private Sub Class_Initialize()
      Dim PerfFrequency As LARGE_INTEGER
      QueryPerformanceFrequency PerfFrequency
      m_crFrequency = LI2Double(PerfFrequency)
    End Sub
    
    Public Sub StartCounter()
      QueryPerformanceCounter m_CounterStart
    End Sub
    
    Public Function PerformanceCount() As Double
      Dim liPerformanceCount As LARGE_INTEGER
      QueryPerformanceCounter liPerformanceCount
      PerformanceCount = LI2Double(liPerformanceCount)
    End Function
    
    Public Function MicroTime() As Double
      MicroTime = Me.PerformanceCount * 1000000# / m_crFrequency
    End Function
    
    Public Property Get TimeElapsed() As Double
      Dim crStart As Double
      Dim crStop As Double
      QueryPerformanceCounter m_CounterEnd
      crStart = LI2Double(m_CounterStart)
      crStop = LI2Double(m_CounterEnd)
      TimeElapsed = 1000# * (crStop - crStart) / m_crFrequency
    End Property
    

    【讨论】:

      【解决方案4】:
      Option Explicit
      
      Sub UpdateSummary()
      
          Dim varData
          Dim objDicCountry As Object
          Dim objDicCity As Object
          Dim objDicData As Object
          Dim lngR As Long
          Dim varResult
          Dim lngC As Long
          Dim strKey As String
          Dim varUnique
      
          varData = Sheet12.Range("A1").CurrentRegion
          Set objDicCity = CreateObject("Scripting.Dictionary")
          Set objDicCountry = CreateObject("Scripting.Dictionary")
          Set objDicData = CreateObject("Scripting.Dictionary")
      
          For lngR = LBound(varData) + 1 To UBound(varData)
      
              strKey = varData(lngR, 1) '--Country
              objDicCountry.Item(strKey) = ""
      
              strKey = varData(lngR, 2) '--City
              objDicCity.Item(strKey) = ""
      
              strKey = varData(lngR, 1) & "|" & varData(lngR, 2) '--Country and City
              objDicData.Item(strKey) = objDicData.Item(strKey) + varData(lngR, 3)
      
          Next lngR
      
          ReDim varResult(1 To objDicCountry.Count + 1, 1 To objDicCity.Count + 1)
      
          varUnique = objDicCountry.keys '--get Unique Country
      
          For lngR = LBound(varUnique) To UBound(varUnique)
              varResult(lngR + 2, 1) = varUnique(lngR)
          Next lngR
      
          varUnique = objDicCity.keys '--get Unique City
      
          For lngC = LBound(varUnique) To UBound(varUnique)
              varResult(1, lngC + 2) = varUnique(lngC)
          Next lngC
      
      
          For lngR = LBound(varResult) + 1 To UBound(varResult)
              For lngC = LBound(varResult) + 1 To UBound(varResult, 2)
                  strKey = varResult(lngR, 1) & "|" & varResult(1, lngC) '--Country & "|" & City
                  varResult(lngR, lngC) = objDicData.Item(strKey)
              Next lngC
          Next lngR
      
          Sheet12.Range("F6").Resize(UBound(varResult), UBound(varResult, 2)).Value = varResult
          MsgBox "Done", vbInformation
      
      End Sub
      

      【讨论】:

      • 感谢您为 Stack Overflow 做出贡献。 OP 要求提供一个简单的表格来比较各种功能。用一堆代码发布答案而没有解释它与问题的关系并不是很有帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多