Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/excel/27.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
“Excel上的VBA”;“内存不足”;错误_Vba_Excel - Fatal编程技术网

“Excel上的VBA”;“内存不足”;错误

“Excel上的VBA”;“内存不足”;错误,vba,excel,Vba,Excel,我在Excel 2010上,在一张公认的非常大的表上(400k行X 20列) 我的守则旨在: 将整个工作表加载到数组中 检查每一行的特定条件 符合条件的行将复制到另一个数组中 最后,将第二个数组返回到另一个工作表 第二个阵列最终将大约是原始阵列的90% 我将两个变量数组定义为变量 并试图通过复制工作表的内容两次来初始化它们 第一个副本有效,但到第二个副本时,我遇到了“内存不足”的错误 有解决办法吗?或者这只是VBA/Excel的一个限制 有没有办法不预先定义/初始化目标阵列,而是让它随着标准

我在Excel 2010上,在一张公认的非常大的表上(400k行X 20列)

我的守则旨在:

  • 将整个工作表加载到数组中
  • 检查每一行的特定条件
  • 符合条件的行将复制到另一个数组中
  • 最后,将第二个数组返回到另一个工作表
  • 第二个阵列最终将大约是原始阵列的90%
我将两个变量数组定义为变量 并试图通过复制工作表的内容两次来初始化它们

第一个副本有效,但到第二个副本时,我遇到了“内存不足”的错误

有解决办法吗?或者这只是VBA/Excel的一个限制

有没有办法不预先定义/初始化目标阵列,而是让它随着标准的每一次成功鉴定而“增长”?(以这样的规模)


正如你帖子上的评论所指出的,这个错误来自于工作记忆的不足

每个变量类型变量消耗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 
子进程行()
Dim originalData()作为变体
Dim maxRow为长,currentRow为长,incrementRow为长
maxRow=ActiveSheet.Usedrange.Rows.Count
currentRow=1
增量行=5000
当currentRow

当然,您可以使用逐行方法,但我假设您使用数组变量来加速代码,因此我不建议使用逐行方法。

逐行工作非常慢,因此对于如此大的数据集,这不是一个可行的解决方案

阵列无疑是一种选择,因此可以在以下两者之间进行选择:

  • 分批加载数据,然后在连续的数据集上运行处理*(直到有大量数据时才可行——根据您的系统,大约有800万个元素)
  • 分批加载数据,然后仅在该批上运行处理(适用于任意数量的数据
  • 编辑:我看到你是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
    
    选项显式
    子示例()
    Dim myCompletedataArr
    将myTestDataRange设置为范围
    设置myTestDataRange=ActiveSheet.UsedRange
    LoadDataInBatch myTestDataRange,myCompletedataArr
    Debug.Assert为False
    端接头
    子加载DataInBatch(dataRange作为Range,dataArr,可选startRow作为Long=1,可选行作为Long=10000)
    暗尾行一样长,我一样长,j一样长
    Dim DATALRLB1为长,DATALRLB2为长,batchArrLb1为长,batchArrLb2为长
    Dim batchArr,batchRange作为范围
    如果不是IsArray(dataArr),则
    ReDim dataArr(0到dataRange.rows.Count-1,0到dataRange.Columns.Count-1)
    “如果”结束,否则假设dataArr的尺寸正确(为简单起见)
    endRow=WorksheetFunction.Min(startRow+rows-1,dataRange.rows.Count)
    
    如果endRow以上所有建议都表明内存不足。我的计算机内存充足,我也曾尝试切换到64位Excel。我必须假设存在一个常见的Excel错误。对我来说,最安全(但不能保证每次都会发生)的方法如下: 由于Excel文件频繁崩溃这一内置效应,我将.xlsx文件中的数据和所有宏分离到加载项中。这种不方便的方法将崩溃几乎降至零。但是,每当我需要对加载项进行升级时,我都会先卸载加载项,然后修改加载项的源文件。保存此.xlsb文件,然后将其另存为。xlam。当我试图关闭外接程序的源文件(即.xlsb文件)时,系统会询问我是否要保存新副本或覆盖更改。无论我选择什么,我都会收到“内存不足”消息。有时我可以继续工作,有时Excel崩溃并启动新的。
    也许这个描述解释了该消息与内存太少或分配的数据太多无关。

    另一种方法是分块攻击,比如说一次50000行,或者为您的计算机添加内存/RAM容量。我使用的是8 GB。在64位的windows上,您也可以有更多内存。我建议稍微增加一点不同的方法:您可以一次处理一行,而不是将所有内容加载到数组中。读取、检查、写入(如果正确),然后转到下一个。这将显著减少VBA的工作内存。您可能需要64位Excel。我因处理大量大型变量而出现内存不足的问题;切换到64位Excel非常有效。根本不需要升级硬件。
    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