Excel VBA解读(138): 自定义函数时使用字节数组实现更快的字符串处理

2019-07-19 11:23:51 浏览数 (1)

学习Excel技术,关注微信公众号:

excelperfect

字符串处理会使VBA变慢。

假设想要找到字符串中第一个大写字母的位置,可以使用数组公式:

代码语言:javascript复制
=MATCH(TRUE,ISERR(FIND(MID(A1,ROW($1:$255),1),LOWER(A1))),0)

如果有很多行,要查找每行字符串第一个大写字母的位置,则使用数组公式会花费不少时间。

如果编写用户自定义函数,则会更快些。代码如下:

代码语言:javascript复制
Function FirstCap2(Cell As Range)
   For FirstCap2 = 1 To Len(Cell.Value)
        If Mid(Cell.Value, FirstCap2, 1) Like"[A-Z]" Then
            Exit For
        End If
   Next FirstCap2
End Function

代码使用Mid遍历字符串,使用LIKE依次检查每个字符是否为大写字母A到大写字母Z之一。

稍微修改一下代码,让其运行更快:

代码语言:javascript复制
Function FirstCap3(Rng As Range)
   Dim theString As String
   theString = Rng.Value2
   For FirstCap3 = 1 To Len(theString)
        If Mid$(theString, FirstCap3, 1) Like"[A-Z]" Then
            Exit For
        End If
   Next FirstCap3
End Function

将代码更改为将字符串从单元格中仅取出一次,并使用Mid$函数而不是Mid函数。所有的VBA字符串处理函数都有2个版本:不带后缀$使用变体参数的版本,和带有$后缀的只能处理字符串参数的版本,后者速度更快。

但是,也许使用LIKE还是慢?尝试比较字符串的小写版本,并在字符不匹配时停止:

代码语言:javascript复制
Function FirstCap4(strInp AsString) As Long
   Dim tmp As String
   Dim i As Long
   Dim pos As Long
   tmp = LCase$(strInp)
   pos = -1
   For i = 1 To Len(tmp)
        If Mid$(tmp, i, 1) <>Mid$(strInp, i, 1) Then
            pos = i
            Exit For
        End If
   Next
   FirstCap4 = pos
End Function

但这个版本比前一个版本更慢!将Byte数组与字符串一起使用是VBA不为人知的秘密之一,当需要依次检查每个字符时,它通常是处理字符串的一种有效方法。

代码语言:javascript复制
Function FirstCap5(theRange AsRange) As Long
   Dim aByte() As Byte
   Dim j As Long
   FirstCap5 = -1
   aByte = theRange.Value2
   For j = 0 To UBound(aByte, 1) Step 2
        If aByte(j) < 91 Then
            If aByte(j) > 64 Then
                FirstCap5 = (j   2) / 2
                Exit For
            End If
        End If
   Next j
End Function

这个版本的自定义函数更快。代码首先创建一个Byte类型的数组,然后将字符串赋给该数组。字符串中的每个字符都有2个字节,英文大写字符的ANSI编号是65到90,因此可以循环这个字节数组,间隔查看其中的字节,并直接对字符进行数字测试,看它是否为大写。

Byte数组另一个令人惊讶的特点是可以直接赋值字节数组到字符串:

Dim str1 as string

str1=aByte

正如在上一篇文章中所讨论的,数组公式更快。因此,给出该用户定义函数的数组公式版本:

代码语言:javascript复制
Function AFirstCap(theRange AsRange) As Variant
   Dim aByte() As Byte
   Dim j As Long
   Dim L As Long
   Dim vRange As Variant
   Dim jAnsa() As Long
   Dim NumCells As Long
   vRange = theRange.Value2
   NumCells = UBound(vRange, 1)
   ReDim jAnsa(NumCells - 1, 0)
   For L = 0 To NumCells - 1
        jAnsa(L, 0) = -1
        aByte = vRange(L   1, 1)
        For j = 0 To UBound(aByte, 1) Step 2
            If aByte(j) < 91 Then
                If aByte(j) > 64 Then
                    jAnsa(L, 0) = (j   2) / 2
                    Exit For
                End If
            End If
        Next j
   Next L
   AFirstCap = jAnsa
End Function

小结:在字符串处理中,需要检查或操作很多单个字符时,使用字节数组是一个很好的解决方案。

0 人点赞