Я пытаюсь выработать относительные преимущества и особенности словарей по сравнению с коллекциями и массивами.
Я нашел отличную статью здесь, но не могу найти простую таблицу, которая сравнивает все различные функции.
Кто-нибудь знает об этом?
Я пытаюсь выработать относительные преимущества и особенности словарей по сравнению с коллекциями и массивами.
Я нашел отличную статью здесь, но не могу найти простую таблицу, которая сравнивает все различные функции.
Кто-нибудь знает об этом?
Пожалуйста, смотрите таблицу ниже для полезного сравнения сборников и словарей.
(Таблица суммирует эту страницу до раздела "Раннее и позднее связывание". К вашему сведению, на странице также есть более подробная информация об использовании словарей)
Таким образом, лучше всего использовать словарь или массив.
При рассмотрении вопроса об использовании коллекций может быть более целесообразным использовать массив, если размер не изменяется или изменяется очень редко. В этом случае массив, вероятно, будет более эффективным, чем коллекция, так как массивы очень эффективны для заполнения и извлечения всех элементов одновременно (например, диапазон до массива и массив до диапазона).
Также обратите внимание:
По сравнению с массивами коллекции обеспечивают хорошую производительность для добавления и вставки элементов, а также для доступа к ним и их удаления с помощью их ключей. Тем не менее, производительность низка, если элементы должны быть доступны по индексу. Информацию о том, как сделать это эффективно, смотрите здесь, где также обсуждаются внутренние механизмы этих объектов списка.
Эта страница cpearson имеет очень полезный код для работы со словарями, коллекциями и массивами (сортировка их, а также преобразование их в друг друга!)
Немного текста со страницы 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!
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
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
Что касается производительности сборников по сравнению со словарями, я обнаружил, что запись в словари выполняется аналогично записи в сборники, чтение из словаря занимает примерно вдвое больше времени, чем чтение из сборника. Во-первых, создание словаря намного медленнее, чем создание коллекции.
Вот результаты, которые я получил за выполнение 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/info/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