0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【VBA】Midステートメントを用いたStringBuilderクラス【コピペで使える】

Posted at

はじめに

VBAで大量の文字列を効率的に連結する必要があったため、Midステートメントを活用したStringBuilderクラスを作成しました。通常の文字列連結(&)と比較して、大幅なパフォーマンス向上を実現できます。
ちなみにMidステートメントとは左辺にMidを用いた値の代入のことで、文字列の一部分だけを置き換えることのできる特殊な仕様を指します。

StringBuilderクラス

Option Explicit

'******************************************************************************
' クラス名  : clsStringBuilder
' 機能      : 予め指定したメモリ領域をバッファとして利用する文字列連結クラス
' 説明      : VBA標準の文字列連結(&)は大規模テキストで非効率なため
'           : StringBuilderパターンを採用
' 注意点    : 文字数の上限は約5.36億桁(Midステートメントの限界が約1GBのため)
' バージョン: 1.0.0
' -----------------------------------------------------------------------------
' 履歴      : 2025/09/27  pecorimaru  Ver.1.0.0  新規作成
'******************************************************************************

'******************************************************************************
' バッファサイズ設定
' ※1文字あたり2バイトであるため、実際に占有するメモリ領域はバッファサイズ*2
'******************************************************************************
Private Const DEFAULT_BUFFER_SIZE As Long = 32768
Private Const GROWTH_FACTOR As Double = 2#         ' バッファ拡張倍率

'******************************************************************************
' モジュール名
'******************************************************************************
Private Const CLASS_NAME = "clsStringBuilder"

'******************************************************************************
' エラー番号
'******************************************************************************
Private Const ERR_NO_INDEX_NEGATIVE = vbObjectError + 1001
Private Const ERR_NO_INDEX_OUT_OF_RANGE = vbObjectError + 1002
Private Const ERR_NO_DELETE_LENGTH_NEGATIVE = vbObjectError + 1003

'******************************************************************************
' エラーメッセージ
'******************************************************************************
Private Const ERR_MSG_INDEX_NEGATIVE = "{0}位置が負の値です"
Private Const ERR_MSG_INDEX_OUT_OF_RANGE = "{0}位置({1})は範囲外です(可能範囲:0-{2})"
Private Const ERR_MSG_DELETE_LENGTH_NEGATIVE = "削除文字数が負の値です"
Private Const ERR_MSG_BUFFER_OVERFLOW = "clsStringBuilderのバッファがサイズ制限を超えた可能性があります。" & vbLf & _
                                        "(現在:{0}桁, 上限:約5.36億桁)"

'******************************************************************************
' クラス変数
'******************************************************************************
Private p_buf As String
Private p_currentLength As Long
Private p_bufferSizeOverCount As Long

'******************************************************************************
' 機 能    : クラスの初期化処理
'******************************************************************************
Private Sub Class_Initialize()
    p_buf = String$(DEFAULT_BUFFER_SIZE, vbNullChar)
    p_currentLength = 0
    p_bufferSizeOverCount = 0
End Sub

'******************************************************************************
' 機 能    : クラスの終了処理
'******************************************************************************
Private Sub Class_Terminate()
    If p_bufferSizeOverCount > 0 Then
        Debug.Print "バッファサイズ超過回数:" + CStr(p_bufferSizeOverCount) + "回"
        Debug.Print "最終文字数/バッファサイズ:" + CStr(Me.Length) + "/" + CStr(Me.Capacity)
    End If
    Call Dispose
End Sub

'******************************************************************************
' 機 能    : 現在の文字数を取得
'******************************************************************************
Public Property Get Length() As Long
    Length = p_currentLength
End Property

'******************************************************************************
' 機 能    : 現在のバッファサイズを取得
'******************************************************************************
Public Property Get Capacity() As Long
    Capacity = Len(p_buf)
End Property

'******************************************************************************
' 機 能    : バッファが空かどうかを判定
'******************************************************************************
Public Property Get IsEmpty() As Boolean
    IsEmpty = (p_currentLength = 0)
End Property

'******************************************************************************
' 機 能    : 全体の文字列を取得(Textプロパティ)
'******************************************************************************
Public Property Get Text() As String
    Text = Me.ToString()
End Property

'******************************************************************************
' 機 能    : 文字列をバッファに追加
' -----------------------------------------------------------------------------
' 引 数    : str - 追加する文字列
' 戻り値    : なし
'******************************************************************************
Public Sub Append(ByVal str As String)
    If str = "" Then
        Exit Sub
    End If
    
    Dim strLength As Long
    strLength = Len(str)
    
    Call EnsureCapacity(strLength)
    
    On Error GoTo ErrorHandler

    ' 末尾に文字列をセット
    Mid(p_buf, p_currentLength + 1) = str
    
    p_currentLength = p_currentLength + strLength
    
    Exit Sub

ErrorHandler:
    Err.Raise Err.Number, CLASS_NAME & ".Append", _
              Err.Description & vbLf & FormatMessage(ERR_MSG_BUFFER_OVERFLOW, p_currentLength + strLength)
End Sub

'******************************************************************************
' 機 能    : バッファ内の全文字列を取得
' -----------------------------------------------------------------------------
' 引 数    : なし
' 戻り値    : String - バッファ内の全文字列(空の場合は空文字列)
'******************************************************************************
Public Function ToString() As String
    ToString = Left$(p_buf, p_currentLength)
End Function

'******************************************************************************
' 機 能    : 文字列と改行をバッファに追加
' 説  明    : 追加する文字列が省略された場合は改行のみを追加する
' -----------------------------------------------------------------------------
' 引 数    : str - 追加する文字列(省略可能)
' 戻り値    : なし
'******************************************************************************
Public Sub AppendLine(Optional ByVal str As String = "")
    Call Me.Append(str)
    Call Me.Append(vbCrLf)
End Sub

'******************************************************************************
' 機 能    : バッファの内容をクリア
' -----------------------------------------------------------------------------
' 引 数    : なし
' 戻り値    : なし
'******************************************************************************
Public Sub Clear()
    p_buf = String$(DEFAULT_BUFFER_SIZE, vbNullChar)
    p_currentLength = 0
End Sub

'******************************************************************************
' 機 能    : 指定位置に文字列を挿入
' -----------------------------------------------------------------------------
' 引 数    : offset - 挿入位置(0ベース)
'           : str    - 挿入する文字列
' 戻り値    : なし
'******************************************************************************
Public Sub Insert(ByVal offset As Long, ByVal str As String)
    ' 挿入位置の負数チェック
    If offset < 0 Then
        Err.Raise ERR_NO_INDEX_NEGATIVE, CLASS_NAME, FormatMessage(ERR_MSG_INDEX_NEGATIVE, "挿入")
    End If
    
    ' 挿入位置の範囲チェック
    If offset > p_currentLength Then
        Err.Raise ERR_NO_INDEX_OUT_OF_RANGE, CLASS_NAME, _
                FormatMessage(ERR_MSG_INDEX_OUT_OF_RANGE, "挿入", offset, p_currentLength)
    End If
    
    ' 挿入位置が末尾の場合はAppendを使用
    If offset = p_currentLength Then
        Call Me.Append(str)
        Exit Sub
    End If
    
    If str = "" Then
        Exit Sub
    End If

    Dim strLength As Long
    strLength = Len(str)

    Call EnsureCapacity(strLength)
    
    On Error GoTo ErrorHandler
    
    ' 挿入位置以降の文字列を挿入する文字数分シフトする
    Mid(p_buf, offset + 1 + strLength) = Mid$(p_buf, offset + 1)
    
    ' 挿入位置に文字列をセット
    Mid(p_buf, offset + 1) = str
    
    p_currentLength = p_currentLength + strLength

    Exit Sub

ErrorHandler:
    Err.Raise Err.Number, CLASS_NAME & ".Insert", _
              Err.Description & vbLf & FormatMessage(ERR_MSG_BUFFER_OVERFLOW, p_currentLength + strLength)
End Sub

'******************************************************************************
' 機 能    : 指定範囲の文字列を削除
' -----------------------------------------------------------------------------
' 引 数    : start        - 削除開始位置(0ベース)
'           : removeLength - 削除する文字数
' 戻り値    : なし
'******************************************************************************
Public Sub Remove(ByVal start As Long, ByVal removeLength As Long)
    ' 削除開始位置の負数チェック
    If start < 0 Then
        Err.Raise ERR_NO_INDEX_NEGATIVE, CLASS_NAME, FormatMessage(ERR_MSG_INDEX_NEGATIVE, "削除開始")
    End If
    
    ' 削除開始位置の範囲チェック
    If start >= p_currentLength Then
        Err.Raise ERR_NO_INDEX_OUT_OF_RANGE, CLASS_NAME, _
                FormatMessage(ERR_MSG_INDEX_OUT_OF_RANGE, "削除開始", start, p_currentLength - 1)
    End If
    
    ' 削除文字数の負数チェック
    If removeLength < 0 Then
        Err.Raise ERR_NO_DELETE_LENGTH_NEGATIVE, CLASS_NAME, ERR_MSG_DELETE_LENGTH_NEGATIVE
    End If
    
    If removeLength = 0 Then
        Exit Sub
    End If

    ' 削除範囲を調整(バッファサイズを超えないように)
    Dim actualLength As Long
    If start + removeLength > p_currentLength Then
        actualLength = p_currentLength - start
    Else
        actualLength = removeLength
    End If
    
    ' 削除開始位置 + 削除する文字数以降の文字列を削除開始位置へシフトする
    Mid(p_buf, start + 1) = Mid$(p_buf, start + actualLength + 1)
    
    ' バッファ内の全文字数 - 削除する文字数以降の文字列をクリアする
    Mid(p_buf, p_currentLength - actualLength + 1) = String$(removeLength, vbNullChar)
    
    p_currentLength = p_currentLength - actualLength

End Sub

'******************************************************************************
' 機 能    : バッファサイズの拡張
' 説 明    : バッファサイズを超過した場合はサイズを拡張して再配置する
'           : -- サイズの拡張方法 --
'           : 現在のバッファサイズ + (初期バッファサイズ * 超過回数 * 拡張倍率) + 追加文字数
' -----------------------------------------------------------------------------------
' 引 数    : strLength - 追加文字数
' 戻り値    : なし
'******************************************************************************
Private Sub EnsureCapacity(ByVal strLength As Long)
    If p_currentLength + strLength > Len(p_buf) Then
        p_bufferSizeOverCount = p_bufferSizeOverCount + 1
        p_buf = p_buf & String$(CLng(DEFAULT_BUFFER_SIZE * p_bufferSizeOverCount * GROWTH_FACTOR) + strLength, vbNullChar)
    End If
End Sub

'******************************************************************************
' 機 能    : リソースの解放処理
' -----------------------------------------------------------------------------
' 引 数    : なし
' 戻り値    : なし
'******************************************************************************
Private Sub Dispose()
    p_buf = vbNullString
    p_currentLength = 0
    p_bufferSizeOverCount = 0
End Sub

'******************************************************************************
' 機 能    : プレースホルダーを置き換えた文字列を取得
' 説  明    : テンプレート文字列内の{0},{1}...を指定された値で置き換える
' 注意事項  : ・プレースホルダーは{0}から開始(0ベース)
'           : ・引数が不足した場合、該当プレースホルダーはそのまま残る
' -----------------------------------------------------------------------------
' 引 数    : template - テンプレート文字列
'           : args     - 置き換える値(可変引数)
' 戻り値    : String   - 置き換え後の文字列
'******************************************************************************
Private Function FormatMessage( _
    ByVal template As String, _
    ParamArray args() As Variant _
) As String
    
    Dim result As String
    Dim i As Integer
    
    result = template
    
    ' 各引数でプレースホルダーを置換
    For i = 0 To UBound(args)
        result = Replace(result, "{" & CStr(i) & "}", CStr(args(i)))
    Next i
    
    FormatMessage = result
    
End Function

制限事項

項目 制限値 理由
最大文字数 約5.36億文字 Midステートメントの限界
メモリ使用量 約1GB UTF-16エンコーディング
Insert/Remove 低パフォーマンス 文字列シフト処理のため

Insert、Removeは大量データで使用するとパフォーマンスが大幅に低下します。
基本的にはAppend中心の使用を推奨します。

使い方

Public Sub test()

    Dim sb As New clsStringBuilder
    
    sb.Append "Append"
    sb.AppendLine "Remove"
    sb.AppendLine "AppendLine"
    sb.Insert 6, "Insert"
    sb.Remove 12, 6
    
    Debug.Print "Length:" & sb.Length
    Debug.Print "Capacity:" & sb.Capacity
    Debug.Print "IsEmpty:" & sb.IsEmpty
    Debug.Print sb.ToString

    sb.Clear

    Debug.Print "Length:" & sb.Length
    Debug.Print "Capacity:" & sb.Capacity
    Debug.Print "IsEmpty:" & sb.IsEmpty
    Debug.Print sb.ToString

End Sub

## 実行結果 ##
Length:26
Capacity:32768
IsEmpty:False
AppendInsert
AppendLine

Length:0
Capacity:32768
IsEmpty:True

パフォーマンスチェック

実施環境

  • Windows 11, Ryzen 7 255(AMD Ryzen 9 8945H相当?), 32GB RAM
  • Microsoft 365 (64bit)
' 3万文字*20列*884行(約5.3億文字)の処理時間を計測
Sub TestPrcsTime()
    
    Dim startTime As Double
    startTime = Timer
    
    Dim sb As New clsStringBuilder

    Dim ary As Variant
    ary = ActiveSheet.Range("A1:U884")
 
    Dim i As Long
    For i = LBound(ary, 1) To UBound(ary, 1)
        Dim j As Long
        For j = LBound(ary, 2) To UBound(ary, 2)
            sb.Append CStr(ary(i, j))
        Next
    Next
    
    Dim str As String
    str = sb.ToString

    Dim endTime As Double
    endTime = Timer
        
    Call PrintPrcsTime(startTime, endTime)

End Sub

' 5文字*106列*1,000,000行(5.3億文字)の処理時間を計測
Sub TestPrcsTime2()
    
    Dim startTime As Double
    startTime = Timer
    
    Dim sb As New clsStringBuilder

    Dim ary As Variant
    ary = ActiveSheet.Range("A1:DB1000000")
 
    Dim i As Long
    For i = LBound(ary, 1) To UBound(ary, 1)
        Dim j As Long
        For j = LBound(ary, 2) To UBound(ary, 2)
            sb.Append CStr(ary(i, j))
        Next
    Next
    
    Dim str As String
    str = sb.ToString

    Dim endTime As Double
    endTime = Timer
        
    Call PrintPrcsTime(startTime, endTime)

End Sub

Public Sub PrintPrcsTime(ByVal startTime As Double, ByVal endTime As Double)

    Dim elapsed As Double
    elapsed = endTime - startTime
    
    Dim mm As Long
    mm = Int(elapsed \ 60)
    
    Dim ss As Long
    ss = Int(elapsed Mod 60)
    
    Dim ms As Long
    ms = (elapsed - Int(elapsed)) * 1000
    
    ' フォーマットして出力
    Debug.Print "処理時間:" & Format(mm, "00") & ":" & Format(ss, "00") & ":" & Format(ms, "000")

End Sub
# TestPrcsTime(3万文字*20列*884行(約5.3億文字)の処理時間を計測)
処理時間:00:24:277
バッファサイズ超過回数:127回
最終文字数/バッファサイズ:530400000/536519376

# TestPrcsTime2(5文字*106列*1,000,000行(5.3億文字)の処理時間を計測)
処理時間:02:00:676
バッファサイズ超過回数:127回
最終文字数/バッファサイズ:530000000/532710011

旧世代PCでも確認

  • Windows 11, Core i5 3340M(Ivy Bridge), 8GB RAM
  • Microsoft 365 (64bit)
# TestPrcsTime(3万文字*20列*884行(約5.3億文字)の処理時間を計測)
処理時間:00:41:068
バッファサイズ超過回数:127回
最終文字数/バッファサイズ:530400000/536519376

# TestPrcsTime2(5文字*106列*1,000,000行(5.3億文字)の処理時間を計測)
処理時間:02:36:922
バッファサイズ超過回数:127回
最終文字数/バッファサイズ:530000000/532710011

パフォーマンスチェックを再現する場合は文字列をセットしたExcelシートを準備する必要があります。

Insert、RemoveはAppendの1/10以下のパフォーマンスであり、多用するとクラッシュする可能性が高いためご注意ください。
例えば、TestPrcsTimeをInsertで実施する場合だと、行数を100行に減らした結果が次の通りです。

' 3万文字*20列*100行(約6千万文字)の処理時間を計測
Sub TestPrcsTime()
    
    Dim startTime As Double
    startTime = Timer
    
    Dim sb As New clsStringBuilder

    Dim ary As Variant
    ary = ActiveSheet.Range("A1:U100")
 
    Dim i As Long
    For i = LBound(ary, 1) To UBound(ary, 1)
        Dim j As Long
        For j = LBound(ary, 2) To UBound(ary, 2)
            sb.Insert 0 CStr(ary(i, j))
        Next
    Next
    
    Dim str As String
    str = sb.ToString

    Dim endTime As Double
    endTime = Timer
        
    Call PrintPrcsTime(startTime, endTime)

End Sub
# TestPrcsTime
処理時間:01:49:781
バッファサイズ超過回数:42回
最終文字数/バッファサイズ:60000000/60471776
最終文字数/バッファサイズ:60000000/60471776

処理の説明(コア部分のみ)

クラスの初期化処理(Class_Initialize)

' 全体
Private Sub Class_Initialize()
    p_buf = String$(DEFAULT_BUFFER_SIZE, vbNullChar)
    p_currentLength = 0
    p_bufferSizeOverCount = 0
End Sub
    ' バッファの初期化処理
    p_buf = String$(DEFAULT_BUFFER_SIZE, vbNullChar)
  • [DEFAULT_BUFFER_SIZE]で指定した桁数分のNull文字をセットしています
  • 「$」には次の意味があり、パフォーマンス向上のために付けています
    • String:Variant型を返す
    • String$:String型を返す

文字列をバッファに追加(Append)

' 全体
Public Sub Append(ByVal str As String)
    If str = "" Then
        Exit Sub
    End If
    
    Dim strLength As Long
    strLength = Len(str)
    
    Call EnsureCapacity(strLength)
    
    On Error GoTo ErrorHandler

    ' 末尾に文字列をセット
    Mid(p_buf, p_currentLength + 1) = str
    
    p_currentLength = p_currentLength + strLength
    
    Exit Sub

ErrorHandler:
    Err.Raise Err.Number, CLASS_NAME & ".Append", _
              Err.Description & vbLf & FormatMessage(ERR_MSG_BUFFER_OVERFLOW, p_currentLength + strLength)
End Sub

ポイント

    ' 末尾に文字列をセット
    Mid(p_buf, p_currentLength + 1) = str
# 処理イメージ

0. 前提
p_buf = "--------------------..." ※-はNull値
str = "ABC"

1. 末尾に文字列(ABC)をセット
before:--------------------...
after :ABC-----------------...

エラーハンドリング
Midステートメントは約5.36億文字を超えると例外が発生するため、clsStringBuilderの中で発生したエラーと認識できるようなエラーハンドリングを実装しています。

    ' 末尾に文字列をセット(第3引数でstrLengthを指定)
    Mid(p_buf, p_currentLength + 1, strLength) = str
  • 上記のように第3引数を指定するのは冗長と考え省略しました
    ※strLength = 3万の場合でもパフォーマンスに違いは見られませんでした

指定位置に文字列を挿入(Insert)

' 全体
Public Sub Insert(ByVal offset As Long, ByVal str As String)
    ' 挿入位置の負数チェック
    If offset < 0 Then
        Err.Raise ERR_NO_INDEX_NEGATIVE, CLASS_NAME, FormatMessage(ERR_MSG_INDEX_NEGATIVE, "挿入")
    End If
    
    ' 挿入位置の範囲チェック
    If offset > p_currentLength Then
        Err.Raise ERR_NO_INDEX_OUT_OF_RANGE, CLASS_NAME, _
                FormatMessage(ERR_MSG_INDEX_OUT_OF_RANGE, "挿入", offset, p_currentLength)
    End If
    
    ' 挿入位置が末尾の場合はAppendを使用
    If offset = p_currentLength Then
        Call Me.Append(str)
        Exit Sub
    End If
    
    If str = "" Then
        Exit Sub
    End If

    Dim strLength As Long
    strLength = Len(str)

    Call EnsureCapacity(strLength)
    
    On Error GoTo ErrorHandler
    
    ' 挿入位置以降の文字列を挿入する文字数分シフトする
    Mid(p_buf, offset + 1 + strLength) = Mid$(p_buf, offset + 1)
    
    ' 挿入位置に文字列をセット
    Mid(p_buf, offset + 1) = str
    
    p_currentLength = p_currentLength + strLength

    Exit Sub

ErrorHandler:
    Err.Raise Err.Number, CLASS_NAME & ".Insert", _
              Err.Description & vbLf & FormatMessage(ERR_MSG_BUFFER_OVERFLOW, p_currentLength + strLength)
End Sub

ポイント

    ' 挿入位置以降の文字列を挿入する文字数分シフトする
    Mid(p_buf, offset + 1 + strLength) = Mid$(p_buf, offset + 1)
    
    ' 挿入位置に文字列をセット
    Mid(p_buf, offset + 1) = str
# イメージ

0. 前提
p_buf = "ABCDEF--------------..." ※-はNull値
offset = 3
str = "INSERT"

1. 挿入位置以降の文字列を挿入する文字数分シフトする
before:ABCDEF--------------...
after :ABCDEF---DEF--------...

2. 挿入位置に文字列をセット
before:ABCDEF---DEF--------...
after :ABCINSERTDEF--------...

指定範囲の文字列を削除(Remove)

' 全体
Public Sub Remove(ByVal start As Long, ByVal removeLength As Long)
    ' 削除開始位置の負数チェック
    If start < 0 Then
        Err.Raise ERR_NO_INDEX_NEGATIVE, CLASS_NAME, FormatMessage(ERR_MSG_INDEX_NEGATIVE, "削除開始")
    End If
    
    ' 削除開始位置の範囲チェック
    If start >= p_currentLength Then
        Err.Raise ERR_NO_INDEX_OUT_OF_RANGE, CLASS_NAME, _
                FormatMessage(ERR_MSG_INDEX_OUT_OF_RANGE, "削除開始", start, p_currentLength - 1)
    End If
    
    ' 削除文字数の負数チェック
    If removeLength < 0 Then
        Err.Raise ERR_NO_DELETE_LENGTH_NEGATIVE, CLASS_NAME, ERR_MSG_DELETE_LENGTH_NEGATIVE
    End If
    
    If removeLength = 0 Then
        Exit Sub
    End If

    ' 削除範囲を調整(バッファサイズを超えないように)
    Dim actualLength As Long
    If start + removeLength > p_currentLength Then
        actualLength = p_currentLength - start
    Else
        actualLength = removeLength
    End If
    
    ' 削除開始位置 + 削除する文字数以降の文字列を削除開始位置へシフトする
    Mid(p_buf, start + 1) = Mid$(p_buf, start + actualLength + 1)
    
    ' バッファ内の全文字数 - 削除する文字数以降の文字列をクリアする
    Mid(p_buf, p_currentLength - actualLength + 1) = String$(removeLength, vbNullChar)
    
    p_currentLength = p_currentLength - actualLength

End Sub

ポイント

    ' 削除開始位置 + 削除する文字数以降の文字列を削除開始位置へシフトする
    Mid(p_buf, start + 1) = Mid$(p_buf, start + actualLength + 1)
    
    ' バッファ内の全文字数 - 削除する文字数以降の文字列をクリアする
    Mid(p_buf, p_currentLength - actualLength + 1) = String$(removeLength, vbNullChar)
# イメージ

0. 前提
p_buf = "ABCDEF--------------..." ※-はNull値
start = 1
removeLength = 3

1. 削除開始位置 + 削除する文字数以降の文字列を削除開始位置へシフトする
before:ABCDEF--------------...
after :AEFDEF--------------...

2. バッファ内の全文字数 - 削除する文字数以降の文字列をクリアする
before:AEFDEF--------------...
after :AEF-----------------...

バッファサイズの拡張(EnsureCapacity)

' 全体
Private Sub EnsureCapacity(ByVal strLength As Long)
    If p_currentLength + strLength > Len(p_buf) Then
        p_bufferSizeOverCount = p_bufferSizeOverCount + 1
        p_buf = p_buf & String$(CLng(DEFAULT_BUFFER_SIZE * p_bufferSizeOverCount * GROWTH_FACTOR) + strLength, vbNullChar)
    End If
End Sub

ポイント

    p_buf = p_buf & String$(CLng(DEFAULT_BUFFER_SIZE * p_bufferSizeOverCount * GROWTH_FACTOR) + strLengthgth, vbNullChar)

やや面倒な計算式になっていますが、イメージは下記のとおりで、
バッファが超過した回数分、追加するバッファを増やす仕様としております。

# イメージ

0. 前提
strLength は軽微な桁数と仮定し、イメージから省略する

□ = 値がNull値のバッファ
■ = 値が文字列のバッファ
※ □ or ■ が1つあたり、初期バッファサイズ(DEFAULT_BUFFER_SIZE)とする

1. 超過1回目
before:■
after :■ □ □

2. 超過2回目
before:■ ■ ■
after :■ ■ ■ □ □ □ □

3. 超過3回目
before:■ ■ ■ ■ ■ ■ ■
after :■ ■ ■ ■ ■ ■ ■ □ □ □ □ □ □

※実際のafterは上記 +strLength 分が拡張される

バッファサイズの拡張処理は初期バッファサイズや拡張量が少ないと頻繁にリサイズが発生してパフォーマンスが落ちてしまいますが、逆に拡張しすぎてもリソースの無駄遣いになるため、両者のバランスを考えて設定する必要があります。
上記を踏まえた上でも、今の仕組みはそこそこバランスが取れた実装になっていると思います。

例えば、パフォーマンスチェックの項で記載した、TestPrcsTimeとTestPrcsTime2を5億桁の初期バッファサイズで実施した場合の結果は次の通りです。

Private Const DEFAULT_BUFFER_SIZE As Long = 536519376 の場合

# TestPrcsTime
処理時間:00:03:555

# TestPrcsTime2
処理時間:01:32:219
Private Const DEFAULT_BUFFER_SIZE As Long = 32768 の場合

# TestPrcsTime(3万文字*20列*884行(約5.3億文字)の処理時間を計測)
処理時間:00:24:277

# TestPrcsTime2(5文字*106列*1,000,000行(5.3億文字)の処理時間を計測)
処理時間:02:00:676

限界値である5億桁を格納する機会はあまりないと思いますが、超過回数に対するパフォーマンスの減衰幅はこの通りです。利用ケースに合わせて初期バッファサイズを調整するのがベターな使い方ではないでしょうか。

まとめ

VBAの文字列結合は頻繁に使用することがあるので一度時間をとって手堅いものを作ろうと思い着手しましたが、記事作成時点でもいろいろと修正したこともあって時間がかかってしまいました。

そんな苦労もあってなかなか無駄のない実装ができたと思っているので、同じ課題を抱える方のお役に立てれば幸いです。

参考リンク

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?