字典与集合(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 Dictionary
Set 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 Variant
Dim iOrdinal As Integer
iOrdinal = 10
vItems = oDictionay.Items
vItem = vItems(iOrdinal)
从Collection对象中删除数据的方法通常是采用For…Each…Next语句,在你初次对Dictionary对象使用For…Each…Next时,可以假设你从未对字典使用过该语句,但是尽管没有当前的记录位置,你仍能够使用For…Each…Next,你只需要Dictionary对象的inter_NewEnum函数返回的与条目有关的键值,而不是象Collection对象那样,需要返回字典条目的索引,你可以将这些键值传递给Item方法以便删除数据成员,如下所示:
Dim sKey As Variant
For Each sKey in oDictionary
VItem = 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 Variant
Items = mdDict.Items
End Property
或者你愿意提供一个更加有意义的名字给封装特性,那么可以这样:
Public Property Get Employees() As Variant
Employees = mdDict.Items
End Property
然后你的客户端程序代码就可以利用For…Each…Next或For…Next循环在可变数组中进行迭代搜索,以下这些代码告诉你怎样才能实现这一点:
Dim oEmployees As Employees ' wrapper class
Dim aEmployees As Variant ' Variant to hold array
Dim oEmp As Employee ' data member class
Dim i As Integer ' simple counter
Set oEmployees = New Employees 'Dictionary wrapper class
aEmployees = oEmployees.Employees 'return an array of objects
For i = lBound(aEmployees) To uBound(aEmployees)
Set oEmp = aEmployees(i)
cboNames.AddItem oEmp.Name
Set oEmp = Nothing
Next 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)
Else
MsgBox "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) = sNewKey
Else
MsgBox "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对象的性能测试比较,你会得到与我大致相同的结果。
如果我可以自由选择(我的客户有足够的时间和金钱),那么我将用Dictionary对象替换所有的Collection对象。这项工作还没有开始,至少本周不会进行,但是从现在开始,我用Visual Basic 编制的所有程序都将采用Dictionary对象。
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 Dictionary
Set 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 Variant
Dim iOrdinal As Integer
iOrdinal = 10
vItems = oDictionay.Items
vItem = vItems(iOrdinal)
从Collection对象中删除数据的方法通常是采用For…Each…Next语句,在你初次对Dictionary对象使用For…Each…Next时,可以假设你从未对字典使用过该语句,但是尽管没有当前的记录位置,你仍能够使用For…Each…Next,你只需要Dictionary对象的inter_NewEnum函数返回的与条目有关的键值,而不是象Collection对象那样,需要返回字典条目的索引,你可以将这些键值传递给Item方法以便删除数据成员,如下所示:
Dim sKey As Variant
For Each sKey in oDictionary
VItem = 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 Variant
Items = mdDict.Items
End Property
或者你愿意提供一个更加有意义的名字给封装特性,那么可以这样:
Public Property Get Employees() As Variant
Employees = mdDict.Items
End Property
然后你的客户端程序代码就可以利用For…Each…Next或For…Next循环在可变数组中进行迭代搜索,以下这些代码告诉你怎样才能实现这一点:
Dim oEmployees As Employees ' wrapper class
Dim aEmployees As Variant ' Variant to hold array
Dim oEmp As Employee ' data member class
Dim i As Integer ' simple counter
Set oEmployees = New Employees 'Dictionary wrapper class
aEmployees = oEmployees.Employees 'return an array of objects
For i = lBound(aEmployees) To uBound(aEmployees)
Set oEmp = aEmployees(i)
cboNames.AddItem oEmp.Name
Set oEmp = Nothing
Next 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)
Else
MsgBox "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) = sNewKey
Else
MsgBox "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对象的性能测试比较,你会得到与我大致相同的结果。
如果我可以自由选择(我的客户有足够的时间和金钱),那么我将用Dictionary对象替换所有的Collection对象。这项工作还没有开始,至少本周不会进行,但是从现在开始,我用Visual Basic 编制的所有程序都将采用Dictionary对象。