VBA学习笔记47_程序优化与提速

今天学习 11 个程序优化与提速的小技巧:

1. 减少对象的调用

调用对象非常耗费资源,因此要尽可能减少对象调用,包括工作簿、工作表、单元格及外引用对象。

在循环内外调用对象对程序运行速度也有影响,尽量在循环外调用对象。

2. 减少计算次数

程序运算速度与计算次数密切相关,因此要尽量减少计算次数。

能在循环外计算出结果的,不要在循环内计算。

3. 禁止闪屏

设置 Application.ScreenUpdatingFalse 可禁止程序运行过程中的屏幕闪烁,从而提高运行速度。

注意:仅对会引起闪屏的操作有效,否则可能拖慢程序速度。关闭屏幕闪烁后,需在操作完成或程序末尾将 Application.ScreenUpdating 设置为 True,否则日常 Excel 操作也会禁止屏幕刷新。

Application.ScreenUpdating = False    '关闭屏幕闪动
Application.ScreenUpdating = True     '开启屏幕闪动

4. 增加变量的声明类型

声明变量时尽量指定变量类型,否则计算机默认将其视为 Variant 类型,并根据赋值自动判断类型,容易造成内存浪费。

数组尤其需要声明变量类型,养成习惯为每个变量声明类型。

复习数据类型:

(1) 字符串 String,简写符号 $,用于保存文本数据,字符串内容需置于双引号内。

(2) 数字类型

  • 整型 Integer,简写符号 %,占用 2 字节,数据范围:-32,768 ~ 32,767。
  • 长整型 Long,简写符号 &,占用 4 字节,数据范围:-2,147,483,648 ~ 2,147,483,647。
  • 单精度浮点型 Single,简写符号 !,占用 4 字节,精度 6 位,保存小数点后最多 6 位数据。
    • 负数范围:-3.402823E38 ~ -1.401298E-45
    • 正数范围:1.401298E-45 ~ 3.402823E38
  • 双精度浮点型 Double,简写符号 #,占用 8 字节,精度 14 位,保存小数点后最多 14 位数据。
    • 负数范围:-1.79769313486231E308 ~ -4.94065645841247E-324
    • 正数范围:4.94065645841247E-324 ~ 1.79769313486231E308

为避免内存浪费,定义数据类型时需选择合适的类型。

(3) 日期型 Date,表示日期和时间。

  • 日期范围:100年1月1日 ~ 9999年12月31日
  • 时间范围:0:00:00 ~ 23:59:59

(4) 布尔型 Boolean,表示逻辑值:True(真)、False(假),常用于条件判断语句。

注意:其他数据类型转换为布尔值时,0 转为 False,其他值转为 True。布尔值转换为其他数据类型时,False 转为 0,True 转为 -1。

(5) 变体型 Variant,几乎可保存所有其他数据类型的数据。当不清楚变量数据类型时,可定义为 Variant,但应尽量避免。

5. 减少工作表函数的使用

调用工作表函数可简化代码,但速度不理想。减少工作表函数调用是提升程序速度的方法之一。

例:统计 A 列中含有“加10分”的字符串次数。

① 使用循环:

Sub C1()
Dim x, arr, k, t, Y
t = Timer
For Y = 1 To 10
    arr = Range("a1:a6800")
    For x = 1 To UBound(arr)
        If InStr(arr(x, 1), "加10分") > 0 Then
            k = k + 1
        End If
    Next x
Next Y
Debug.Print Timer - t & "秒"
End Sub

用时:0.05 秒

② 使用函数:

Sub C2()
Dim x, arr, k, t, Y
t = Timer
For Y = 1 To 10
    arr = Range("a1:a6800")
    k = Application.CountIf([a:a], "*加10分*")
Next Y
Debug.Print Timer - t & "秒"
End Sub

用时:0.52 秒

使用工作表函数慢了约 10 倍。

6. 减少 VBA 函数的使用

减少 VBA 函数的使用也能提高程序运行速度。

例:整除数值。

① 使用整除运算符:

Sub D1()
Dim x, t, k
t = Timer
For x = 1 To 1000000
    k = 10000 \ 3
Next x
Debug.Print Timer - t
End Sub

用时:0.015 秒

② 使用 VBA 函数:

Sub 用VBA函数算整除()
Dim x, t, k
t = Timer
For x = 1 To 1000000
    k = Int(10000 / 3)
Next x
Debug.Print Timer - t
End Sub

用时:0.031 秒

使用 VBA 函数慢了约一倍。

7. 使用静态数组替换动态数组

直接声明足够大的静态数组比声明动态数组再重新定义要快,因动态数组占用更多内存,即使后续重新定义也较慢。

8. 填充前先清空

填充新数据前,先清空旧数据再填充会更快。这也是编程习惯,若旧数据无用,先清除再填充,以免数据未更新导致错误。

9. 批量替代个体

尽量进行批量操作,尤其在数据量大时,反复单个操作会频繁调用内存,导致程序变慢甚至卡机。

例:判断工作表名称并隐藏。

① 循环判断每个工作表名称再隐藏:

Sub T1()
Dim x As Integer, t
'显示工作表
t = Timer
For x = 1 To Sheets.Count
    If Sheets(x).Name Like "Sh*" Then
        Sheets(x).Visible = False
    End If
Next x
Debug.Print Timer - t
End Sub

用时:0.125 秒

② 将工作表名称放入数组判断后批量隐藏:

Sub 隐藏工作表2()
Dim x As Integer, t, arr(), k
'显示工作表
t = Timer
For x = 1 To Sheets.Count
    If Sheets(x).Name Like "Sh*" Then
        k = k + 1
        ReDim Preserve arr(1 To k)
        arr(k) = Sheets(x).Name
    End If
Next x
Sheets(arr).Visible = False
Debug.Print Timer - t
End Sub

用时:0.047 秒

10. 减少循环次数

减少循环次数是最直接的优化速度方法,但操作难度较大,需学习相关知识,如:

① 利用 VBA 字典替代循环查找,参见 VBA 80 集字典部分。

② 学习 VBA 算法,减少不必要循环和运算,如第 45 集换位算法和第 29 集下棋法。

第 26 集排序算法通过排除不必要运算和循环加快程序运行速度。

11. 巧妙填充公式

向单元格输入公式时,若为连续区域,可使用填充方法完成。

例:在 E 列金额列填充公式,金额 = 数量 * 单价。

f489d9e0-4cda-4332-9492-49999bcca451

① 通过循环填充公式:

Sub F1()
Dim x, t
t = Timer
Range("e2:e1000") = ""
For x = 2 To 1000
    Cells(x, "e") = "=C" & x & "*D" & x
Next x
Debug.Print Timer - t
End Sub

用时:0.125 秒

② 使用 FillDown 方法向下填充:

Sub 填充公式方法2()
Dim x, t
Range("e2:e2000") = ""
t = Timer
Cells(2, "e") = "=C2*D2"
Range("e2:e2000").FillDown
Debug.Print Timer - t
End Sub

用时:0.016 秒


上个月与知友 Erik H 讨论了一个数据类型问题:

Sub test()
Dim x As Long
x = 2000 * 365
End Sub

程序报错:数据溢出。

e466ef00-3112-4167-ae77-f47d1a4ef4ee

按理,x = 2000 * 365 = 730,000,在 Long 的范围 -2,147,483,648 ~ 2,147,483,647 内,为何溢出?

7cbb0d39-b5a4-412d-987c-b05c92211e7d

通过 Watch 窗口查看数据类型,发现常量 2000 和 365 的数据类型为 Integer,说明系统默认给常量分配了数据类型。

VBA 中,等式运算逻辑是将右边值赋值给左边变量,即将 2000 * 365 的结果赋值给 x。

不给 x 赋值,测试结果:

13f62b50-245d-4296-aeaa-0559613402d4

仍报溢出,说明与 x 类型无关,2000 * 365 计算时出错。两个 Integer 相乘,系统默认结果为 Integer,而 730,000 已超出 Integer 范围。

将 2000 和 365 定义为 Long 后不再报错:

Sub test()
Dim x As Long, a As Long, b As Long
a = 2000
b = 365
x = a * b
End Sub

因此,编写程序时需考虑运算结果的数值范围,以免溢出。


小结

上述例子可能不够贴切,仅为简单说明,希望大家能理解这些要点。

最近为工作编写了一些程序,如合并工作簿,自己的代码不够完善。看到公众号大神写的合并工作簿程序,包含检查步骤,如检查要合并的工作簿是否已打开。若无此检查,程序可能报错。要逐渐养成良好的编程思维和习惯,多学多练。

以上,晚安。

Leave a Reply

Your email address will not be published. Required fields are marked *