返回列表 发帖

[原创] 在 VBScript 中使用动态数组(ArrayList)

本帖最后由 老刘1号 于 2023-7-24 10:58 编辑

环境要求


  • Windows XP 及以上。
  • Windows 10Windows 11Windows 功能 中勾选 .NET Framework 3.5 (包括 .NET 2.0 和 3.0)

优点


  • 相比 VBScript 内置的数组,大小可自动变化。
  • 原生支持尾部添加、插入、删除、修改、切片、范围删除、翻转、查询、克隆等操作,免去了手动编写相关逻辑。
  • 可方便的转换为 VBScript 内置的数组。

使用

创建一个 ArrayList 对象:
Set oArr = CreateObject("System.Collections.ArrayList")COPY
Add 方法:在动态数组末尾添加元素

均摊时间复杂度 O(1)
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add Empty
oArr.Add Null
oArr.Add "String"
oArr.Add 0
oArr.Add 3.14
oArr.Add CreateObject("Scripting.FileSystemObject")
oArr.Add New RegExpCOPY
Count 属性:表示动态数组当前元素个数

时间复杂度 O(1)
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.Count()COPY
0COPY
oArr.Add 8
WSH.Echo oArr.Count()COPY
1COPY
Item 属性:表示动态数组的各元素

接收一个整数作为索引,下标从 0 开始。

时间复杂度 O(1)
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add Empty
oArr.Add Null
oArr.Add "String"
oArr.Add 0
oArr.Add 3.14
oArr.Add CreateObject("Scripting.FileSystemObject")
WSH.Echo oArr.Item(4)COPY
3.14COPY
oArr.Item(4) = 3.1415926
WSH.Echo oArr.Item(4)COPY
3.1415926COPY
Set oArr.Item(4) = CreateObject("Scripting.FileSystemObject")
WSH.Echo TypeName(oArr.Item(4))COPY
FileSystemObjectCOPY
Default 默认属性:表示动态数组的各元素

Item 属性。
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add Empty
oArr.Add Null
oArr.Add "String"
oArr.Add 0
oArr.Add 3.14
oArr.Add CreateObject("Scripting.FileSystemObject")
WSH.Echo oArr(4)COPY
3.14COPY
oArr(4) = 3.1415926
WSH.Echo oArr(4)COPY
3.1415926COPY
Set oArr(4) = CreateObject("Scripting.FileSystemObject")
WSH.Echo TypeName(oArr(4))COPY
FileSystemObjectCOPY
Capacity 属性:表示动态数组目前的容量

若容量不足,会自动扩容并拷贝原来的元素。

扩容的时间复杂度是O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.Count, oArr.CapacityCOPY
0 0COPY
For i = 1 To 100
    oArr.Add i
Next
WSH.Echo oArr.Count, oArr.CapacityCOPY
100 128COPY
oArr.Capacity = oArr.Count
WSH.Echo oArr.Count, oArr.CapacityCOPY
100 100COPY
IsFixedSize 属性:数组是否为固定大小

会返回 False
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.IsFixedSize()COPY
0COPY
IsReadOnly 属性:数组是否为只读

会返回 False
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.IsReadOnly()COPY
0COPY
IsSynchronized 属性:表示是否同步对数组的访问

由于 VBScript 是单线程的,此属性无意义。

会返回 False
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.IsSynchronized()COPY
0COPY
Clear 方法:清空动态数组
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 6
WSH.Echo oArr.Count()COPY
1COPY
oArr.Clear
WSH.Echo oArr.Count()COPY
0COPY
Clone 方法:返回该动态数组的拷贝

时间复杂度O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 666
Set oArr2 = oArr.Clone
oArr2.Add 888
WSH.Echo oArr Is oArr2, oArr.Count(), oArr2.Count()COPY
0 1 2COPY
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 666
Set oArr2 = oArr
oArr2.Add 888
WSH.Echo oArr Is oArr2, oArr.Count(), oArr2.Count()COPY
-1 2 2COPY
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add New RegExp
oArr.Add CreateObject("Scripting.FileSystemObject")
Set oArr2 = oArr.Clone
WSH.Echo oArr Is oArr2, oArr(0) Is oArr2(0), oArr(1) Is oArr2(1)COPY
0 -1 -1COPY
ToArray 方法:将动态数组转为普通 VBScript 数组

时间复杂度 O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 1
oArr.Add 3.1415926
oArr.Add True
WSH.Echo Join(oArr.ToArray(), " ")COPY
1 3.1415926 TrueCOPY
Contains 方法:检查动态数组内是否包含特定元素

时间复杂度O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 2333
oArr.Add Null
oArr.Add oArr
WSH.Echo oArr.Contains(2333), oArr.Contains(233), oArr.Contains(Null)COPY
-1 0 -1COPY
WSH.Echo oArr.Contains(oArr), oArr.Contains(CreateObject("System.Collections.ArrayList"))COPY
-1 0COPY
GetRange 方法:返回数组的一个切片

参数:起始下标、切片长度。

时间复杂度 O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 0 To 9
    oArr.Add i
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 2 3 4 5 6 7 8 9COPY
Set oArr2 = oArr.GetRange(2, 5)
WSH.Echo Join(oArr2.ToArray(), " ")COPY
2 3 4 5 6COPY
Insert 方法:插入元素

时间复杂度O(n)

参数:下标、元素。
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 0
oArr.Add 5
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 5COPY
oArr.Insert 1, 4
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 4 5COPY
oArr.Insert 3, 6
oArr.Insert 1, 2
oArr.Insert 2, 3
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 2 3 4 5 6COPY
Remove 方法:移除指定的元素

时间复杂度 O(n)

注意:


  • 只会移除从头至尾第一个遇到的和指定元素相等的元素。
  • 如果没有找到指定的元素,那么什么也不会发生。
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 1 To 5
    oArr.Add i Mod 2
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
1 0 1 0 1COPY
oArr.Remove 1
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 0 1COPY
oArr.Remove 2
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 0 1COPY
RemoveAt 方法:移除指定位置的元素

时间复杂度 O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 1 To 5
    oArr.Add i Mod 2
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
1 0 1 0 1COPY
oArr.RemoveAt 0
oArr.RemoveAt 1
oArr.RemoveAt 2
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 0COPY
RemoveRange 方法:移除指定区间的元素

参数:起始下标、长度。

时间复杂度 O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 0 To 5
    oArr.Add i
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 2 3 4 5COPY
oArr.RemoveRange 3, 2
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 2 5COPY
Reverse 方法:翻转数组

时间复杂度 O(n)
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 0 To 9
    oArr.Add i
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
0 1 2 3 4 5 6 7 8 9COPY
oArr.Reverse
WSH.Echo Join(oArr.ToArray(), " ")COPY
9 8 7 6 5 4 3 2 1 0COPY
Sort 方法:将数组内元素排序

时间复杂度 O(n * log(n))
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 0 To 9
    oArr.Add Fix(Rnd() * 100)
Next
WSH.Echo Join(oArr.ToArray(), " ")COPY
70 53 57 28 30 77 1 76 81 70COPY
oArr.Sort()
WSH.Echo Join(oArr.ToArray(), " ")COPY
1 28 30 53 57 70 70 76 77 81COPY
TrimToSize 方法:使容量缩减为正好与当前元素个数相等
Set oArr = CreateObject("System.Collections.ArrayList")
For i = 0 To 9
    oArr.Add Fix(Rnd() * 100)
Next
WSH.Echo oArr.Capacity(), oArr.Count()COPY
16 10COPY
oArr.TrimToSize
WSH.Echo oArr.Capacity()COPY
10COPY
LastIndexOf 方法:返回数组中指定元素的索引

若有重复的元素,则返回最靠后的指定元素的索引。

若未找到元素,则返回 -1
Set oArr = CreateObject("System.Collections.ArrayList")
oArr.Add 1
WSH.Echo oArr.LastIndexOf(1)COPY
0COPY
oArr.Add(1)
WSH.Echo oArr.LastIndexOf(1), oArr.LastIndexOf(2)COPY
1 -1COPY
ToString 方法:返回类名
Set oArr = CreateObject("System.Collections.ArrayList")
WSH.Echo oArr.ToString(), TypeName(oArr)COPY
System.Collections.ArrayList ArrayListCOPY
参考

回复 1# 老刘1号


    但,用vbs写个动态数组类也不难啊?尤其是栈,反倒最简单,倒是队列最麻烦

TOP

回复 2# jyswjjgdwtdtj


    动态数组队列堆栈都不难,但是人家net是编译好的,vbs实现的临时解释运行速度上不去
哈希表有点复杂,但也不难
有序数组就很难自己实现了,不过我之前试过用vbs写avl树,github上有代码
net的有序数组应该是红黑树写的

TOP

回复 3# 老刘1号

谁用vbs处理大量数据啊🤔

TOP

测个效率(插入100000个数据
dictionary:
a=timer
set d=createobject("scripting.dictionary")
for i=0 to 100000
d.add i,0
next
b=d.keys()
msgbox timer-aCOPY
0.13
原生动态数组:
a=timer
dim d()
redim d(-1)
for i=0 to 100000
redim preserve d(i)
d(i)=0
next
b=ubound(d)
msgbox timer-aCOPY
0.13(和上面的几乎一致
原生普通数组:
a=timer
dim d(100000)
for i=0 to 100000
d(i)=0
next
msgbox timer-aCOPY
0.007
js数组:
c=timer
set html=createobject("htmlfile")
Set window = html.parentWindow
window.execScript "var j=new Array()"
set a=window.j
for i=0 to 100000
a.push 0
next
msgbox timer-cCOPY
0.14(转换为vbs数组需要用到new vbarray 时间会再长一点
wia vector:
c=timer
set v=createobject("wia.vector")
for i=0 to 100000
v.add 0
next
msgbox timer-cCOPY
0.51
.net arraylist:
c=timer
Set a = CreateObject("System.Collections.ArrayList")
for i=0 to 100000
a.add 0
next
msgbox timer-cCOPY
0.4

当然 各有千秋 比如arraylist里的一大堆方法 原生的速度 vector的byte() js数组同时可以作为栈 队列等等
你好

TOP

回复 5# jyswjjgdwtdtj


    可以试试更大的数量级
我这里的测试数据
c=timer
Set a = CreateObject("System.Collections.ArrayList")
for i=0 to 1000000
a.add 0
next
msgbox timer-c
a=timer
dim d()
redim d(-1)
for i=0 to 1000000
redim preserve d(i)
d(i)=0
next
b=ubound(d)
msgbox timer-aCOPY
5.6
22.7
也就是说在大数量级上频繁redim的效率很低
因为redim的底层实现其实是新开一块空间并把原来的拷贝进去
一个优化过的版本
a=timer
dim d()
redim d(-1)
for i=0 to 1000000
if i > ubound(d) then
redim preserve d(i * 2)
end if
d(i)=0
next
redim preserve d(1000000)
b=ubound(d)
msgbox timer-aCOPY
0.2
对你没看错,只用了0.2s

TOP

回复 5# jyswjjgdwtdtj


    再扯远一点,数据数量级扩大1,耗时扩大2个数量级,说明是O(n^2)的时间复杂度
数量级扩大1,耗时扩大1个数量级说明是O(n)的时间复杂度
可以测出NET的ArrayList和我的实现都是O(n)的,而你的频繁ReDim的实现是O(n^2)的
其实几乎所有语言的动态数组的标准库实现也是使用较为惰性的增减空间的(类似我那种)
之前和别人在VBS群里讨论的时候就谈过这个事情,和我讨论的那个人还说:官方示例就是那么写的
我只能说官方示例里面也有坑哈哈

TOP

回复 7# 老刘1号


    嗯 为啥选这么个较小的数 是因为如果加个0dictionary会卡着不动就很怪

一般来说用动态数组都会像你这么说地“惰性” 比如要遍历文件夹啥的

之前老是听说dictionary要比动态数组慢 这么看似乎还要快些?
你好

TOP

回复 8# jyswjjgdwtdtj


    dict底层都是哈希表实现,哈希表增删改查时间复杂度都是 O(1) 的。
缺点嘛,就是耗内存。
哈希表只能有效使用10%~20%的内存空间,元素再多性能就会明显下降,所以一般的语言标准库实现都会选择扩容。
算法嘛就是两条原则,时间换空间,或者空间换时间。

TOP

返回列表