excelVBA字典与集合(Dictionary与Collection)
Dictionary对象将替换Collection对象,并提供附加的语⾔从⽽使增加和删除记录的速度⽐以前提⾼三倍,虽然Visual Basic 6.0只有很少的新特点,但是具有某些功能强⼤的新的对象模型,其中之⼀就是Dictionary对象。
Dictionary对象是⽆处不在的Visual Basic Collection对象的新版本。它的介绍存在于VBScript 2.0,并通过Visual Basic 6.0对Scripting Runtime Library的⽀持涉⼊Visual Basic的全部内容。刚开始,Dictionary对象仅仅包含在VBScript中,并作为Perl相关内容的等价体对Web组请求进⾏答复。
与Collection对象相似,你能够通过Dictionary存储任何类型的数据或字典对象,这些数据和对象通常被看作字典的组成部分,每⼀部分都被赋予字符串型键值。虽然我不认为Microsoft意图使你完全摆脱收集和替换上述数据和对象的烦恼,但是实际上,在先前的Visual Basic 6.0⽂档中,对Dcitionary对象确实很少提及,因此我认为这是Visual Basic 6.0的⼀个最新的重要特点。
Dictionary对象与Collection对象的⽐较
从Visual Basic 4.0开始,Collection对象就作为主要的数据类型替代了⽤户⾃⼰定义的类型,由此以后,⼤多数Visual Basic程序都包含Collection对象。如果你从Visual Basic 4.0开始已经⾮常习惯于使⽤Collection对象,那么你⼜为什么需要作出改变呢?这主要有⼏个因素:
Dictionary对象⽐Collection 对象更快,这种速度优势主要体现在增加数据成员、在字典中进⾏迭代搜索和删除数据成员上。
Dictionary对象包括那些你经常不得不⾃⼰编制的封装函数,例如Exists函数和RemoveAll函数。
Dictionary对象让你能够创建Key值数组和Item值数组,从⽽加快在字典中进⾏迭代搜索的速度。
Dictionary对象让你能够覆盖已经存在的Key值和已经存在的数据成员。 Dictionary对象还确实存在着下述缺点,但它们本⾝并不
值⼀提:与Collection对象不同,Dictionary对象不是VBA语⾔DLL的⼀部分,这意味着你需要借助SCRRUN.DLL,并将之连接到相应的应⽤程序。Dictionary对象实现For…Each…Next循环的⽅法也很奇怪,它不是返回Item值,⽽是返回Key值。
Dictionary对象还有⼀恼⼈之处,就是如果你想从字典中删除⼀个没有搜索到的成员,你就必须添加数据到这个空的条⽬或不存在的键。
访问Dictionary对象正如我先前所说,Dictionary对象不是VBA或Visual Basic实时语⾔的具体存在的部分,它是存在于Microsoft Scripting Runtime
Library(SCRRUN.DLL)中的⼀个对象。为了在应⽤程序中使⽤Dictionary对象,就必须利⽤Reference对话框增加⼀个项⽬级的引⽤到Scripting Runtime Library。
增加完引⽤之后,创建Dictionary对象的实例,如下:Dim oDict As DictionarySet oDict = New Dictionary' Do some work.Set oDict = Nothing为了增加⼀个成员到Dictionary对象,利⽤Add⽅法,其中包括两个参数:需要增加的数据和与数据相关联的字符串型Key值,语法如下: dictionary.Add Key, Data在Dictionary 对象中没有指明新的数据成员存放位置的参数,它将由字典⾃⼰挑出。你还需要注意Add⽅法的参数正好与Collection对象的Add⽅法相反,在Collection对象中:collection.Add Data, [Key], [before], [after]与Collection对象类似,Dictionary对象的成员能够是任何数据类型、对象或其他字典,从⽽使你能够按照⾃⼰的意愿任意嵌套Dictionary对象。
访问Dictionary对象的成员
Dictionary对象的Item⽅法是访问包含在字典中数据的推荐⽅法,其好处是速度快,⾮常快。我所做的测试表明,访问Dictionary对象数据成员的速度要⽐访问Collection对象数据成员的速度倍。如果你打开对象浏览器,选择Dictionary对象,并观察隐藏的成员,你就会看到名为HashVal的属性,这表明Dictionary 对象存在⽆⽤信息列表和⼀些奇怪的排队算法。
在设计Dictionary对象时,主要是利⽤将字符串型Key值作为⼀个参数传递给Item的⽅法来实现对数据
的访问,这⼀点与Collection对象相
似,例如,你可以利⽤:VItem = oDictionary.Item(sKey)
这⼉警告⼀句,如果试图利⽤⼀个并不存在的键值返回Collection成员的数值,将会出错(code 5, Invalid Procedure Call or Argument)。Dictionary对象并不这样,它在插⼊该新成员时,采⽤并不存在的键值对应某个键同时⽤零长度字符串对应数据成员。Dictionary对象总是检查你要使⽤的键是否存在于字典内,可以想象,这⼀特点能够轻易地捕捉不经意所犯的错误,⾄于检查键值存在性⽅法将在本⽂的后续内容中述及。
当使⽤Collection对象时,你不能直接顺序地访问字典中的数据,但是使⽤字典的Item⽅法时就不这样,你能够快速地创建所有数据成员的数组,并利⽤该数组顺序地访问所有数据:Dim vItems As VariantDim iOrdinal As IntegeriOrdinal = 10vItems = oDictionay.Items
vItem = vItems(iOrdinal)从Collection对象中删除数据的⽅法通常是采⽤For…Each…Next语句,在你初次对Dictionary对象使⽤For…Each…Next时,可以假设你从未对字典使⽤过该语句,但是尽管没有当前的记录位置,你仍能够使⽤For…Each…Next,你只需要Dictionary对象的inter_NewEnum函数返回的与条⽬有关的键值,⽽不是象Collection对象那样,需要返回字典条⽬的索引,你可以将这些键值传递给Item⽅法以便删除数据成员,如下所⽰: Dim sKey As VariantFor Each sKey in oDictionaryVIt
em = oDictionary.Item(sKey)
…Next
当你在封装类中利⽤Dictionary对象时,存在另⼀个使⽤For…Each…Next的次要关键。你不能在客户端使⽤For…Each…Next循环对数据成员进⾏迭代搜索,除⾮你愿意进⾏⼤量的复杂编程。其原因是Dictionary对象的internal_NewEnum函数不是⼀个隐含成员,⽽在Collection对象中它是,它不能通过Visual Basic调⽤,因此你不能够在封装类实现⾃⼰的_NewEnum函数,简单的Set NewEnum = mCol.[_NewEnum]语句不能与Dictionary对象共同⼯作,但是,使⽤Dcitionary对象获得的诸多好处使这种折中⾮常值得。
那么,怎样访问Dictionary对象封装类的每⼀个成员呢?Dictionary对象包含名为Items的⽅法,该⽅法返回所有Dictionary对象成员的⼀个可变数组,你只需要在⾃⼰的类中提供⼀个封装⼦程序以返回Item数组:Public Property Get Items() As VariantItems =mdDict.ItemsEnd Property
vba编程技巧或者你愿意提供⼀个更加有意义的名字给封装特性,那么可以这样:Public Property Get Employees() As VariantEmployees = mdDict.ItemsEnd Property然后你的客户端程序代码就可以利⽤For…Each…Next或For…Next循环在
可变数组中进⾏迭代搜索,以下这些代码告诉你怎样才能实现这⼀点:Dim oEmployees As Employees ' wrapper classDim aEmployees As Variant ' Variant to hold arrayDim oEmp As Employee ' data member classDim i As Integer ' simple counterSet oEmployees = New Employees 'Dictionary wrapper classaEmployees = oEmployees.Employees 'return an array of objectsFor i = lBound(aEmployees) To uBound(aEmployees)Set oEmp =
aEmployees(i)cboNames.AddItem oEmp.NameSet oEmp = NothingNext i
Set oEmployees = Nothing
那么性能怎样呢?当在同样的机器上调⽤动态连接库时,结合Dictionary封装类的Item数组和Foe…Each…Next的迭代搜索不如仅仅运⽤Collection封装类进⾏的迭代搜索快,但是如果你处理的是远程或进程外的服务程序,那么情况刚好相反。利⽤Dictionary的封装类,你只是进⾏简单数组的简单转换,⽽Collection类则反复调⽤远程服务程序,每⼀个迭代都要进⾏过程调⽤。我设置了⼀个简单的实验以考察远程Dictionary对象和Collection对象的迁移性,这些对象包括1000个简单的字符串成员并利⽤它们迁移⼀个客户端Form的列表,Dictionary对象迁移该列表只需要四分之⼀秒,⽽Collection对象迁移该列表则耗费了差不多三秒钟。
你的成员存在吗?
我反复抱怨Collection对象的⼀个因素是其没有能⼒让你预先知道Collection对象的某⼀个成员是否存在,如果该成员的键值并不存在,那么你就不得不处理出现的错误。由于这个原因,我通常利⽤⼀个类来封装我的Collection对象成员,并使它们包括Exists属性。
不管怎样,Microsoft使Dictionary对象具有Exists⽅法。Exists⾮常便于使⽤,并返回True或False,如下所⽰:If oDictionary.Exsits(sKey) Then' The key is there .vVal = oDictionary.Item(sKey)ElseMsgBox "The key doesn't exist"
End If由于Dictionary对象总是为成员添加⼀个键值和⼀个空字符串,所以当你试图返回⼀个并不存在键值的条⽬时,你总是能够在返回该条⽬之前利⽤Exists⽅法来检测它的存在性(如上⾯例⼦所⽰),这个特点使你免于直接访问⼀个并不存在的键值。
键值覆盖
如果你曾经试图改变某个与Collection对象成员对应的键值,那么你知道这不可能。当对象成员加⼊到Collection对象时,该成员的数据和键值就已经被固定下来了。你能做的唯⼀选择就是使⽤Remove⽅法清除该成员并增加⼀个新成员到该对象。但是,你能够利⽤Dictionary对象的Key特性来覆盖该键的键值,如下例所⽰:If oDictionary.Exists(sOldKey) Then' The key is there .oDictionary.Key(sOldKey) = sNewKeyElseMsgBox "The key dosen't exsit"End If
成员覆盖
我猜想Microsoft在编制Collection对象时,他们假设Collection对象的成员⼀旦加⼊就不再改变,他们为什么会认为开发⼈员仅仅与静态数据打交道呢?!因此,改变Collection对象成员的唯⼀办法就是先从Collection对象中删除它们并重新加⼊。
与Key特性相似,你能够利⽤存在于表达式两边的Dictionary对象的Item特性。在⼀个表达式的右边,你返回对象成员的值,⽽在表达式的左边,你可以设置成员的值,⽅法如下:
If oDictionary.Exists(sKey) Then
' The key is there .
oDictionary.Item(sKey) = vNewItem
Else
MsgBox "The key doesn't exist"
End If
补充
当你需要字典内所有键值的数组时,Item⽅法和Key⽅法也能够帮助你。Item⽅法可以返回包含字典内所有数据成员的可变数组,⽽Key⽅法则可以返回包含字典内所有键值的可变数组。 Dictionary对象的其他特性包括返回字典内成员数⽬的Count特性和能够让你控制内部搜索执⾏情况的CompareMode特性,还有Remove特性和RemoveAll特性,正如其名字所⽰,它们⽤于清除字典内的数据成员。
总结
Dictionary对象与Collection对象相⽐,是⼀个⾮常有价值的尝试。它不但速度快,⽽且具有许多特性,使你从原来不得不⾃⼰编制封装类的烦恼中解脱出来。虽然⽤Dictionary对象替换Collection对象还需要⼀些次要的记录技术(根据For…Each…Next等⽽定),但是利⽤Dictionary对象所带来的性能上的提⾼⾜以补偿这些努⼒。本专题的PROFESSIONAL RESOURCE CD包含⼀个例⼦类,从⽽向你展现样围绕Dictionary对象创建⼀个名为DictCLass.CLS的封装类,它还包括⼀个例⼦应⽤程序,该例⼦向你展⽰怎样利⽤这些类来获得超出于你应⽤程序的强⼤功能。
Collection相当普及,⼤部分Visual Basic数据类都源于此类,⽽Dictionary对象是重要的改进,在添加和删除对象成员⽅⾯要⽐Collection对象倍,你能够戏剧性地提⾼应⽤程序的性能。你也可以⾃⼰进⾏Dictionary对象和Collection对象的性能测试⽐较,你会得到与我⼤致相同的结果。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。