Excel VBA数组进阶三连:从“会用”到“会玩”,看完你就是办公室最靓的仔!
写在前面:如果你还不会数组基础,请先面壁三分钟
上篇文章发出后,有个同事问我:“大哥,我按照你的代码写了,但是报错‘下标越界’,是不是Excel瞧不起我?”
我问他:“你的数组声明的啥?”
他说:“Dim arr(1 To 10)”
我问他:“你往里塞了多少数据?”
他说:“11行…”
朋友,数组不是海绵宝宝,挤不下别硬塞啊!
如果你连数组的声明、赋值、读取都还迷迷糊糊,建议先翻回去补课。今天这篇文章,是给那些已经能用数组跑起来,但想跑得更骚的同学准备的。
第一课:一维数组 vs 二维数组,你还在傻傻分不清?
很多同学学了数组,依然搞不清什么时候用一维,什么时候用二维。
一维数组:就是一根烤串。一串羊肉串,你只能从第一块吃到最后一块,顺序排列。
二维数组:是一张火锅桌。横着数有座位,竖着数也有座位,想坐哪桌坐哪桌。
看代码:
‘ 一维数组:适合处理单行或单列
Dim 烤串(1 To 10) As String
烤串(1) = “羊肉”
烤串(2) = “牛肉”
‘ 这叫串串香
‘ 二维数组:适合处理表格区域
Dim 火锅桌(1 To 5, 1 To 4) As String
火锅桌(2, 3) = “毛肚” ‘ 第2行第3列,毛肚的位置
‘ 这叫火锅局
灵魂拷问:当你从Excel区域取值时,得到的是几维数组?
arr = Range(“A1:A10”).Value ‘ 这是几维?
brr = Range(“A1:D10”).Value ‘ 这是几维?
crr = Range(“A1”).Value ‘ 这又是啥?
答案:
A1:A10:虽然只有一列,但VBA会把它变成二维数组!(10行,1列)
A1:D10:标准的二维数组 (10行,4列)
A1:只有一个单元格,VBA会把它变成二维数组!(1行,1列)
扎心真相:从工作表取出来的数据,永远是二维数组,哪怕只有一行一列。
这就是为什么很多新手写 arr = Range(“A1:A10”).Value 后,想用 arr(1) 取值,结果报错——你应该用 arr(1, 1)!
第二课:数组的“降维打击”,学会了你就是大神
既然从工作表取出来的永远是二维数组,那我想处理一列数据怎么办?
方案A:硬着头皮用二维
arr = Range(“A1:A10”).Value
For i = 1 To 10
Debug.Print arr(i, 1) ‘ 永远记得加个逗号1
Next i
方案B:降维打击(Transpose大法)
arr = Range(“A1:A10”).Value
‘ 使用WorksheetFunction.Transpose进行降维
一维数组 = Application.Transpose(arr)
For i = 1 To 10
Debug.Print 一维数组(i) ‘ 现在可以像烤串一样直接取了
Next i
注意:Transpose是Excel的工作表函数,在VBA里用Application.Transpose调用。它能把:
单列二维数组 → 一维数组(列转行)
单行二维数组 → 一维数组(行转列)
二维区域 → 转置(行列互换)
但是:Transpose有容量限制(约65537个元素),超了会报错。这是Excel的历史包袱,别问,问就是微软的锅。
第三课:动态数组的骚操作,ReDim Preserve你真的会用吗?
动态数组就像伸缩裤腰带——吃多了能放长,饿瘦了能收紧。
Dim arr() As Variant
ReDim arr(1 To 5) ‘ 先来5个位置
‘ 填数据…
For i = 1 To 5
arr(i) = i * 10
Next i
‘ 突然发现还要再加3个位置
ReDim Preserve arr(1 To 8) ‘ Preserve保留原有数据,裤腰带放长三格
但是:ReDim Preserve有个巨坑——它只能修改最后一维的大小!
什么意思?
‘ 二维数组
Dim brr() As Variant
ReDim brr(1 To 5, 1 To 3)
‘ 想增加列数?可以!
ReDim Preserve brr(1 To 5, 1 To 4) ‘ 没问题,最后一维从3变4
‘ 想增加行数?报错!
ReDim Preserve brr(1 To 6, 1 To 3) ‘ 报错!不能改第一维!
为什么?
微软说:这是底层设计,我也没办法。(其实就是懒)
解决方案:要么一开始把行数估计大一点,要么用Transpose把行列互换后再Preserve,完事儿再转回来。
这操作就像脱裤子放屁,但有时候真管用:
‘ 骚操作:先转置,让想增加的那一维变成最后一维
brr转置 = Application.Transpose(brr)
ReDim Preserve brr转置(1 To 3, 1 To 6) ‘ 现在可以增加行了(原列)
brr = Application.Transpose(brr转置) ‘ 再转回来
警告:这招慎用,数据量大了转两次挺费时间。但面试时说出来,面试官会觉得你是个狠人。
第四课:数组与字典的“神仙组合”,查找速度快到飞起
数组处理数据快,但有个致命弱点——查找慢。
你想在10000个数据里找“张三”,用数组得循环10000次。虽然比单元格循环快,但还是不够优雅。
这时候,字典(Dictionary) 出场了。
把字典想象成酒店前台:
数组:你得一间一间敲门问“张三住哪?”
字典:前台小姐姐一秒告诉你“308房”
组合拳打法:
‘ 先引用:工具→引用→勾选 Microsoft Scripting Runtime
Sub 数组加字典()
Dim arr As Variant
Dim dic As New Dictionary
Dim i As Long
‘ 把数据装进数组
arr = Range(“A1:B10000”).Value
‘ 遍历数组,把关键信息存进字典
For i = 1 To UBound(arr)
‘ 把姓名作为key,所在行号作为item
dic(arr(i, 1)) = i
Next i
‘ 秒查“李四”在哪一行
If dic.Exists(“李四”) Then
MsgBox “李四在第” & dic(“李四”) & “行,他的成绩是” & arr(dic(“李四”), 2)
Else
MsgBox “查无此人”
End If
End Sub
威力:
数组负责批量运输数据
字典负责秒级查找数据
两者结合,又快又准
这就好比:你开着卡车(数组)把一仓库货拉到分拣中心,然后让AI机器人(字典)帮你找货。这才是现代办公的姿势!
第五课:数组与数组之间的“联姻”,你必须要懂的合并技巧
有时候,你需要把两个数组合并成一个。
比如:
数组A:员工姓名
数组B:员工工资
想要:姓名+工资一一对应的二维数组
青铜操作:循环赋值
Dim arr(1 To 10, 1 To 2)
For i = 1 To 10
arr(i, 1) = 姓名数组(i)
arr(i, 2) = 工资数组(i)
Next i
王者操作:利用Index函数(需要想象力)
‘ 假设有两个一维数组
姓名 = Array(“张三”, “李四”, “王五”)
工资 = Array(5000, 8000, 6000)
‘ 构建二维数组
ReDim 结果(1 To 3, 1 To 2)
For i = 1 To 3
结果(i, 1) = 姓名(i)
结果(i, 2) = 工资(i)
Next i
‘ 没有捷径,该循环还得循环
但是:如果想把两个二维数组横向拼接(加列),或者纵向拼接(加行),那就有意思了。
纵向拼接(加行):
‘ 数组A:1-5行,数组B:6-10行
ReDim 结果(1 To 10, 1 To 列数)
For i = 1 To 5
For j = 1 To 列数
结果(i, j) = 数组A(i, j)
Next j
Next i
For i = 1 To 5
For j = 1 To 列数
结果(i + 5, j) = 数组B(i, j)
Next j
Next i
横向拼接(加列):
‘ 数组A:1-3列,数组B:4-6列
ReDim 结果(1 To 行数, 1 To 6)
For i = 1 To 行数
For j = 1 To 3
结果(i, j) = 数组A(i, j)
Next j
For j = 1 To 3
结果(i, j + 3) = 数组B(i, j)
Next j
Next i
核心思想:数组合并,本质上是手动搬家。没有一键合并的函数,得自己当搬运工。
第六课:数组内存管理,让你的代码再快50%
数组用得好不好,关键看你会不会清理内存。
坏习惯:
Sub 坏习惯()
Dim arr As Variant
arr = Range(“A1:D10000”).Value
‘ 处理数据…
‘ 结束时啥也不干
End Sub
‘ 子程序结束后,arr还在内存里赖着不走
好习惯:
Sub 好习惯()
Dim arr As Variant
arr = Range(“A1:D10000”).Value
‘ 处理数据…
‘ 用完就扔
Erase arr ‘ 清空数组,释放内存
‘ 或者
Set arr = Nothing ‘ 对象用这个,数组用Erase
End Sub
什么时候重要:
处理小数据:无所谓
处理十万行以上:不释放内存,运行多次后Excel会崩
循环里反复创建数组:必须释放,不然内存泄漏
进阶技巧:用Nothing vs Erase
Erase arr:把数组里的数据清空,数组本身还在(可以ReDim重用)
Set arr = Nothing:彻底销毁对象(用于字典、集合等)
数组的生死:
出生:Dim或ReDim
活着:arr = 数据
失忆:Erase
超度:子程序结束 or Set arr = Nothing
记住了:写完代码,记得给数组办身后事。这是专业和业余的分水岭。
第七课:实战案例——用数组制作“超级查询器”
学了这么多,不来个硬核实战说不过去。
需求:有两个工作表,“数据源”有10万行销售记录,“查询表”有1000个产品ID,需要快速找出每个产品的销量。
青铜做法:
For i = 1 To 1000
ID = Sheets(“查询表”).Cells(i, 1)
For j = 1 To 100000
If Sheets(“数据源”).Cells(j, 1) = ID Then
Sheets(“查询表”).Cells(i, 2) = Sheets(“数据源”).Cells(j, 2)
Exit For
End If
Next j
Next i
‘ 运行时间:下班前点运行,第二天早上看结果
王者做法(数组+字典):
Sub 超级查询器()
Dim 数据源 As Variant
Dim 查询表 As Variant
Dim dic As New Dictionary
Dim i As Long
Dim startTime As Double
startTime = Timer
‘ 1. 把数据源装进数组
数据源 = Sheets(“数据源”).Range(“A1:B” & Sheets(“数据源”).Cells(Rows.Count, 1).End(xlUp).Row).Value
‘ 2. 建立字典索引
For i = 1 To UBound(数据源)
‘ 假设A列是ID,B列是销量
If Not dic.Exists(数据源(i, 1)) Then
dic(数据源(i, 1)) = 数据源(i, 2)
End If
Next i
‘ 3. 处理查询表
查询表 = Sheets(“查询表”).Range(“A1:B” & Sheets(“查询表”).Cells(Rows.Count, 1).End(xlUp).Row).Value
For i = 1 To UBound(查询表)
If dic.Exists(查询表(i, 1)) Then
查询表(i, 2) = dic(查询表(i, 1))
Else
查询表(i, 2) = “未找到”
End If
Next i
‘ 4. 写回结果
Sheets(“查询表”).Range(“A1:B” & UBound(查询表)).Value = 查询表
‘ 5. 清理
Erase 数据源
Erase 查询表
Set dic = Nothing
MsgBox “查询完成,耗时:” & Timer – startTime & “秒”
End Sub
效果:
青铜:几分钟到几小时
王者:0.5秒以内
这就是为什么说:用数组和不用数组,是两个物种。
写在最后:数组之路,道阻且长,行则将至
今天的进阶课就到这里。
如果你看到这里还没关掉,说明你是个有追求的人。你不再满足于“能跑就行”,你想的是“更快、更稳、更骚”。
数组的终极奥义,不是记住那些语法,而是理解:
数据在哪里处理最快? 在内存里。
怎么把数据弄进内存? 用数组。
怎么在内存里玩出花? 数组+字典+算法。
课后作业:
把你之前写的单元格循环代码,全部改成数组操作
试着用字典去重(提示:把数据丢进字典,key自动去重)
挑战:10万行数据,找出所有重复项并标记,限时1秒
评论区交作业,我会随机翻牌点评。要是你的代码够骚,我喊你一声大哥!
夜雨聆风