簡體   English   中英

Excel上的VBA“內存不足”錯誤

[英]VBA on Excel "Out of Memory" error

我在 Excel 2010 上,在一張公認的非常大的工作表上(400k 行 X 20 列)。

我的代碼旨在:

  • 將整個工作表加載到一個數組中
  • 根據特定條件檢查每一行
  • 符合條件的行被復制到另一個數組
  • 最后將第二個數組返回到另一個工作表
  • 第二個數組最終將是原始數組的大約 90%

我將兩個變量數組定義為變體,並嘗試通過復制工作表的內容兩次來初始化它們。

第一個副本有效,但到第二個副本時,我遇到了“內存不足”的錯誤。

如果有解決方法,有什么想法嗎? 或者這只是 VBA/Excel 的限制。

有沒有辦法不預先定義/初始化目標數組,而是讓它隨着標准的每個成功限定而“增長”? (在這個規模上)。

Sub CopyPending()
Dim LastRow As Long
Dim LastCol As Integer
Dim AllRange() As Variant
Dim CopyRange() As Variant
Dim i As Long
Dim x As Long
Dim z As Long

LastCol = 21
LastRow = ActiveSheet.UsedRange.Rows.Count

AllRange = Range(Cells(2, 1), Cells(LastRow, LastCol)).Value
CopyRange = Range(Cells(2, 1), Cells(LastRow, LastCol)).Value ''' ERROR TRIGGER

i = 1
x = 1
z = 1

For i = LBound(AllRange) To UBound(AllRange) - 1
  If AllRange(i, 7) = "TestCriteria" Then
    For z = 1 To LastCol
      CopyRange(x, z) = AllRange(i, z)
    Next z
    x = x + 1
  End If
Next i

With Sheets(2)
  .Range(.Cells(2, 1), .Cells(x, LastCol)).Value = CopyRange
End With

End Sub

正如您對帖子的評論所示,此錯誤來自工作記憶不足。

每個 Variant 類型變量占用 16 個字節,這就是您的代碼需要大量內存的原因。 因此,解決此問題的一種方法是增加計算機上的物理內存。

其他解決方案是按一定數量的行過濾數據。

Sub ProcessRows()
    Dim originalData() As Variant
    Dim maxRow as Long, currentRow as Long, incrementRow

    maxRow = ActiveSheet.Usedrange.Rows.Count
    currentRow =1
    incrementRow=5000

    While currentRow < maxRow
        Set originalData = Range(.Cells(currentRow,1),.Cells(currentRow+incrementRow-1,20)

        your process to filter data

        currentRow = currentRow +incrementRow
    Wend
End Sub 

當然,您可以使用逐行方法,但我假設您使用數組變量來加速代碼,因此我不建議使用逐行方法。

逐行工作非常慢,因此對於如此大的數據集,這不是一個可行的解決方案。

數組絕對是要走的路,所以選擇是:

  1. 批量加載數據,然后在連續數據集上運行您的處理 *(直到大量數據才可行 - 可能大約 8M 元素取決於您的系統)
  2. 批量加載數據,然后僅在批處理上運行處理(適用於任意數量的數據

編輯:我看到您是 400k * 20,這正在推動選項 1 的邊界。您可能別無選擇,只能重構代碼並逐批加載和處理(與逐批加載然后一起處理)

筆記:

  • 這應該沒問題,直到非常大的數據集,因為內存不足錯誤起初不是來自數組本身的大小,而是來自工作表的讀取
  • 如果從數組本身的大小中得到內存不足錯誤,則:
    • 您別無選擇,只能使用 64 位 Excel;
    • 或者(更好)重構您的程序以分塊處理數據(上面的選項 2)。

下面通過遞歸批量加載數據,將數據批量加載到單個數組中。 試試看 - 最后仍然有一個數組的好處意味着您不必重構其余的代碼。

選項 1 示例:

Option Explicit

Sub example()

    Dim myCompletedataArr
    Dim myTestDataRange As Range

    Set myTestDataRange = ActiveSheet.UsedRange

    loadDataInBatches myTestDataRange, myCompletedataArr

    Debug.Assert False

End Sub


Sub loadDataInBatches(dataRange As Range, dataArr, Optional startRow As Long = 1, Optional rows As Long = 10000)
    Dim endRow As Long, i As Long, j As Long
    Dim dataArrLb1 As Long, dataArrLb2 As Long, batchArrLb1 As Long, batchArrLb2 As Long
    Dim batchArr, batchRange As Range

    If Not IsArray(dataArr) Then
        ReDim dataArr(0 To dataRange.rows.Count - 1, 0 To dataRange.Columns.Count - 1)
    End If 'otherwise assume dataArr is correctly dimensioned (for simplicity)

    endRow = WorksheetFunction.Min(startRow + rows - 1, dataRange.rows.Count)

    If endRow <= startRow Then Exit Sub

    Set batchRange = dataRange.rows(startRow & ":" & endRow)

    batchArr = batchRange.Value

    'cache lower bounds as we use them a lot
    dataArrLb1 = LBound(dataArr, 1): dataArrLb2 = LBound(dataArr, 2)
    batchArrLb1 = LBound(batchArr, 1): batchArrLb2 = LBound(batchArr, 2)

    For i = batchArrLb1 To UBound(batchArr, 1)
        For j = batchArrLb2 To UBound(batchArr, 2)
            dataArr(startRow - 1 + i + dataArrLb1 - batchArrLb1, j + dataArrLb2 - batchArrLb2) = batchArr(i, j)
        Next j
    Next i
    Erase batchArr 'free up some memory before the recursive call

    loadDataInBatches dataRange, dataArr, endRow + 1, rows

End Sub

上面的所有建議都表明內存不足。 我的電腦有足夠的內存,我也曾經嘗試切換到 64 位 Excel。 我必須假設有一個常見的 Excel 錯誤。 對我來說最安全的方法(不保證每次都發生)如下:由於我的 Excel 文件經常崩潰的內置效果,我將 .xlsx 文件中的數據和所有宏分離到 AddIns 中。 這種不方便的方式將崩潰幾乎減少到零。 但是,每當我需要對 AddIn 進行更新時,我首先卸載 AddIn,然后修改 AddIn 的源文件,保存這個 .xlsb 文件,然后將其另存為 .xlam。 當我嘗試關閉插件的源文件(.xlsb 文件)時,系統會詢問我是要保存新副本還是覆蓋更改。 無論我采取什么選擇,我都會收到“內存不足”的消息。 有時我可以繼續工作,有時 Excel 崩潰並重新開始。 也許這個描述解釋了該消息與內存太少或分配的數據太多無關。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM