【问题标题】:Efficient Data Transfer from Excel VBA to Web-Service从 Excel VBA 到 Web 服务的高效数据传输
【发布时间】:2019-02-28 01:56:37
【问题描述】:

我有一个大型工作表(~250K 行,22 列,~40MB 纯数据),它必须将其内容传输到 Intranet API。格式无关紧要。问题是:当访问像

这样的数据时
Const ROWS = 250000
Const COLS = 22

Dim x As Long, y As Long
Dim myRange As Variant
Dim dummyString As String
Dim sb As New cStringBuilder

myRange = Range(Cells(1, 1), Cells(ROWS, COLS)).Value2

For x = 1 To ROWS
    For y = 1 To COLS
        dummyString = myRange(x, y) 'Runtime with only this line: 1.8s
        sb.Append dummyString 'Runtime with this additional line 163s
    Next
Next

我得到了一个很棒的二维数组,但我无法有效地收集数据以进行 HTTP 导出。 数组上的 X/Y 循环和访问 myRange[x, y] 的运行时间 > 1 分钟。我找不到有助于获取 2D 数组的内爆/编码内容的数组方法。 我目前的解决方法是误用剪贴板 (Workaround for Memory Leak when using large string),它工作得很快,但在我看来是一个肮脏的解决方法,并且有一个主要问题:我得到的值是格式化的,“.Value”而不是“.Value2”,所以我必须在使用前再次转换服务器站点上的数据,例如将货币单元格格式化为浮点数。

处理数据数组的另一个想法是什么?

【问题讨论】:

  • myRange 是什么类型的对象?我怀疑您将其声明为 Range 对象,而不是实际的变体。如果您声明为变体,则循环遍历它会快得多。 See example here
  • 请显示您现有的循环代码。有一个“stringbuilder”类的 VBA 实现,这可能是你想要的。还有“250M”行 - 你的意思是 250k 吗?
  • Scott:是的,它是一个变种,set 被错误地复制了。蒂姆:250K,不是 250M,谢谢。我用完整的测试功能更新了问题
  • 您可能希望将Dim sb As New cStringBuilder 替换为Dim sb As cStringBuilder: Set sb = new cstringbuilder。所以每次引用对象的实例都不需要检查。
  • 我认为您的 stringbuilder 类/对象正在使用某种形式的 join 来减少连接不可变字符串的开销——因此相当有效。

标签: vba excel large-data


【解决方案1】:

我的想法是创建两个字符串数组ABA 的大小可以是1 to ROWSB 的大小可以是1 to COLUMNS。当您遍历 myRange 数组中的每一行时,用该行中每一列的值填充 B 中的每个元素。在该行的最后一列之后和移动到下一行之前,加入数组B 并分配给A 中的行。使用这种大小的循环,只需将必要的东西放入循环本身。最后你会加入A。将项目分配给B 时,您可能需要使用cstr()

Matschek (OP) 能够基于上述编写代码,但为了其他人的利益,代码本身可能类似于:

Option Explicit

Private Sub concatenateArrayValues()

    Const TOTAL_ROWS As Long = 250000
    Const TOTAL_COLUMNS As Long = 22

    Dim inputValues As Variant
    inputValues = ThisWorkbook.Worksheets("Sheet1").Range("A1").Resize(TOTAL_ROWS, TOTAL_COLUMNS).Value2

    ' These are static string arrays, as OP's use case involved constants.
    Dim outputArray(1 To TOTAL_ROWS) As String ' <- in other words, array A
    Dim interimArray(1 To TOTAL_COLUMNS) As String ' <- in other words, array B

    Dim rowIndex As Long
    Dim columnIndex As Long

    ' We use constants below when specifying the loop's limits instead of Lbound() and Ubound()
    ' as OP's use case involved constants.
    ' If we were using dynamic arrays, we could call Ubound(inputValues,2) once outside of the loop
    ' And assign the result to a Long type variable
    ' To avoid calling Ubound() 250k times within the loop itself.

    For rowIndex = 1 To TOTAL_ROWS
        For columnIndex = 1 To TOTAL_COLUMNS
            interimArray(columnIndex) = inputValues(rowIndex, columnIndex)
        Next columnIndex
        outputArray(rowIndex) = VBA.Strings.Join(interimArray, ",")
    Next rowIndex

    Dim concatenatedOutput As String
    concatenatedOutput = VBA.Strings.Join(outputArray, vbNewLine)

    Debug.Print concatenatedOutput

    ' My current machine isn't particularly great
    ' but the code above ran and concatenated values in range A1:V250000
    ' (with each cell containing a random 3-character string) in under 4 seconds.

End Sub

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-26
    • 2015-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多