Ошибка индекса вне диапазона при записи массива из текстового файла в электронную таблицу

У меня есть функция в электронной таблице Excel, которая извлекает данные из txt-файла фиксированной ширины и вставляет их в электронную таблицу. В настоящее время функция сохраняет каждый набор данных в массиве и вставляет его построчно. Однако я приближаюсь к моменту, когда текстовый файл содержит 90 000 строк, а время обработки макроса увеличивается. Я пытался оптимизировать макрос так, чтобы вместо того, чтобы идти построчно, он сохранял все данные в массив и вставлял их все сразу, чтобы уменьшить количество вызовов макроса на лист. Вот исходный код:

Function ImportFixedWidth(FileName As String, _
        StartCell As Range, _
        IgnoreBlankLines As Boolean, _
        SkipLinesBeginningWith As String, _
                SkipLinesBeginningWith2 As String, _
        ByVal FieldSpecs As String) As Long

    Dim FINdx As Long
    Dim c As Long
    Dim r As Range
    Dim FNum As Integer
    Dim s As String
    Dim RecCount As Long
    Dim FieldInfos() As String
    Dim FInfo() As String
    Dim N As Long
    Dim T As String
    Dim B As Boolean
    Dim InfoParts As Variant
    Dim rowData As Variant

    Application.EnableCancelKey = xlInterrupt
    On Error GoTo EndOfFunction:

    If Dir(FileName, vbNormal) = vbNullString Then
        ' file not found
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If Len(FieldSpecs) < 3 Then
        ' invalid FieldSpecs
        ImportFixedWidth = -1
        Exit Function
    End If
        
    If StartCell Is Nothing Then
        ImportFixedWidth = -1
        Exit Function
    End If
       
    Set r = StartCell(1, 1)
    c = r.Column
    FNum = FreeFile
  
    FieldInfos = Split(FieldSpecs, "|")
    
    Open FileName For Input Access Read As #FNum

    If StrComp(Right(FieldSpecs, 1), "|", vbBinaryCompare) = 0 Then
        FieldSpecs = Left(FieldSpecs, Len(FieldSpecs) - 1)
    End If
    
    Do
        ' read the file
        Line Input #FNum, s
        If (SkipLinesBeginningWith <> vbNullString And StrComp(Left(Trim(s), Len(SkipLinesBeginningWith)), SkipLinesBeginningWith, vbTextCompare) And SkipLinesBeginningWith2 <> vbNullString And StrComp(Left(Trim(s), Len(SkipLinesBeginningWith2)), SkipLinesBeginningWith2, vbTextCompare)) Then
            If Len(s) = 0 Then
                If IgnoreBlankLines = False Then
                    Set r = r(2, 1)
                Else
                    ' do nothing
                End If
            Else
              
    
                If FieldSpecs = vbNullString Then
                  
                Else
                
                    If ImportThisLine(s) = True Then
                        FINdx = LBound(FieldInfos)
                        ReDim rowData(1 To UBound(FieldInfos) + 1)
                        c = r.Column
                    
                  Do While FINdx <= UBound(FieldInfos)
                InfoParts = Split(FieldInfos(FINdx), ",")
            rowData(FINdx - LBound(FieldInfos) + 1) = Mid(s, CLng(InfoParts(0)), CLng(InfoParts(1)))
              
        FINdx = FINdx + 1
    Loop
    r.Offset(RecCount, 0).Resize(1, UBound(rowData)).Value = rowData
      

                    End If
                    Set r = r(2, 1)
                End If
            End If
        Else
            ' no skip first char
        End If
        
    Loop Until EOF(FNum)
    
EndOfFunction:
    If Err.Number = 0 Then
        ImportFixedWidth = RecCount
    Else
        ImportFixedWidth = -1
    End If
    Close #FNum
    
End Function

Я попытался обновить код следующим образом, чтобы попытаться оптимизировать производительность:

Function ImportFixedWidth(FileName As String, _
                          StartCell As Range, _
                          IgnoreBlankLines As Boolean, _
                          SkipLinesBeginningWith As String, _
                          SkipLinesBeginningWith2 As String, _
                          ByVal FieldSpecs As String) As Long
    Dim FINdx As Long
    Dim c As Long
    Dim r As Range
    Dim FNum As Integer
    Dim s As String
    Dim RecCount As Long
    Dim FieldInfos() As String
    Dim InfoParts As Variant
    Dim rowData As Variant
    Dim allData() As Variant
    Dim numRows As Long
    
    Application.EnableCancelKey = xlInterrupt
    On Error GoTo EndOfFunction:
    
    If Dir(FileName, vbNormal) = vbNullString Then
        ' file not found
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If Len(FieldSpecs) < 3 Then
        ' invalid FieldSpecs
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If StartCell Is Nothing Then
        ImportFixedWidth = -1
        Exit Function
    End If
    
    Set r = StartCell(1, 1)
    c = r.Column
    FNum = FreeFile
    
    FieldInfos = Split(FieldSpecs, "|")
    
    Open FileName For Input Access Read As #FNum
    
    If StrComp(Right(FieldSpecs, 1), "|", vbBinaryCompare) = 0 Then
        FieldSpecs = Left(FieldSpecs, Len(FieldSpecs) - 1)
    End If
    
    Do
        ' read the file
        Line Input #FNum, s
        If (SkipLinesBeginningWith <> vbNullString And StrComp(Left(Trim(s), Len(SkipLinesBeginningWith)), SkipLinesBeginningWith, vbTextCompare)) And (SkipLinesBeginningWith2 <> vbNullString And StrComp(Left(Trim(s), Len(SkipLinesBeginningWith2)), SkipLinesBeginningWith2, vbTextCompare)) Then
            If Len(s) = 0 Then
                If IgnoreBlankLines = False Then
                    Set r = r(2, 1)
                Else
                    ' do nothing
                End If
            Else
                If FieldSpecs = vbNullString Then
                Else
                    If ImportThisLine(s) = True Then
                        FINdx = LBound(FieldInfos)
                        ReDim rowData(1 To UBound(FieldInfos) + 1)
                        c = r.Column
                        
                        Do While FINdx <= UBound(FieldInfos)
                            InfoParts = Split(FieldInfos(FINdx), ",")
                            rowData(FINdx - LBound(FieldInfos) + 1) = Mid(s, CLng(InfoParts(0)), CLng(InfoParts(1)))
                            FINdx = FINdx + 1
                        Loop
                        RecCount = RecCount + 1
                        ReDim Preserve allData(1 To RecCount)
                        allData(RecCount) = rowData
                    End If
                    Set r = r(2, 1)
                End If
            End If
        Else
            ' no skip first char
        End If
    Loop Until EOF(FNum)
    
    If RecCount > 0 Then
        numRows = UBound(allData, 1)
        StartCell.Resize(numRows, UBound(allData, 2)).Value = allData
    End If
    
EndOfFunction:
    If Err.Number = 0 Then
        ImportFixedWidth = RecCount
    Else
        ImportFixedWidth = -1
    End If
    Close #FNum
End Function

Однако я постоянно получаю ошибку «Индекс вне диапазона» в следующей строке кода:

    If RecCount > 0 Then
        numRows = UBound(allData, 1)
        StartCell.Resize(numRows, UBound(allData, 2)).Value = allData
    End If

Я наблюдаю за кодом, и массив заполняется, как и ожидалось, данными из txt-файла. Я уверен, что есть простое решение, но есть ли что-то, чего мне не хватает, что могло бы устранить ошибку «Индекс вне диапазона»? Огромное спасибо за помощь!

Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
0
66
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

  • ReDim Preserve allData(1 To RecCount) — это одномерный массив. UBound(allData, 2) выдает ошибку времени выполнения 9.

  • StartCell.Resize(numRows) получает целевой диапазон ( numRows Строки x 1 столбец).

  • Application.Transpose(allData) преобразовать одномерный массив в соответствии с целевым диапазоном.

Пытаться

    If RecCount > 0 Then
        numRows = UBound(allData, 1)
        StartCell.Resize(numRows).Value = Application.Transpose(allData)
    End If

  • Как записать одномерный массив в одну строку или столбец на листе?
Sub Demo()
    Dim data As Variant
    data = Array(1, 2, 3) ' 1-D array
    Range("A1:C1").Value = data
    Range("A2:A4").Value = Application.Transpose(data)
End Sub

Спасибо за помощь, я ценю это! Это определенно на правильном пути, однако приведенное выше предложение переносит мои данные вертикально, а не горизонтально. Кроме того, он также помещает только первый набор данных вместо всех наборов данных. Есть ли способ вставить данные так, чтобы они были горизонтальными, а не вертикальными?

Larcen60 05.06.2024 14:38

Конечно, попробуйте StartCell.Resize(,numRows).Value = allData.

taller 05.06.2024 16:44

Спасибо еще раз за помощь! Я попытался обновить его до StartCell.Resize(,numRows).Value = allData, однако это не вставило никаких значений в электронную таблицу. Интересно то, что когда я обновил код до StartCell.Resize(,numRows).Value = Application.Transpose(allData), он вставил первое значение каждого массива горизонтально в первой строке во все столбцы. Есть ли причина, по которой использование только «allData» не вставляет ничего, а «Application.Transpose(allData)» вставляет первое значение из каждого массива в первую строку по столбцам?

Larcen60 05.06.2024 17:12

Является ли StartCell ячейкой или ячейками?

taller 05.06.2024 17:41

StartCell определяется как одна ячейка («A5»).

Larcen60 05.06.2024 17:42

Я загрузил фрагмент. Надеюсь, вам было полезно понять, как записать массив на лист.

taller 05.06.2024 17:45

Это помогло, большое спасибо! Однако массив по-прежнему не вставляет данные в электронную таблицу, даже после того, как я обновил StartCell до диапазона, а не до отдельной ячейки. Однако, когда я изменил StartCell на диапазон, он, однако, вставил больше строк, когда я выполнил «Application.Transpose(allData)». Если у меня есть массив с большим количеством строк для вставки (90 000), нужно ли мне убедиться, что диапазон охватывает все эти строки? Кажется, если я это сделаю, работа электронной таблицы немного замедлится. Еще раз спасибо!

Larcen60 05.06.2024 18:05

Уточните, пожалуйста, значение the range encompasses all those rows ? Запись массива (90 тыс. элементов) на лист в моем тесте занимает всего 0,04 секунды. У вас могут быть другие проблемы с производительностью.

taller 05.06.2024 18:14

Спасибо большое за помощь, выше, твой снипп очень помог с изучением массивов! Ответ Тима ниже работает именно так, как я надеялся!

Larcen60 05.06.2024 20:59

ОП – к вашему сведению Application.Transpose имеет ограничение (65536) на количество элементов, которые вы можете ему передать. Выше этого возвращаемое значение становится усеченным

Tim Williams 06.06.2024 23:23
Ответ принят как подходящий

Это работает для меня. Данные накапливаются и записываются блоками до тех пор, пока не будет обработан весь файл.

Sub tester()
    Debug.Print ImportFixedWidth("C:\Temp\test.txt", [A1], False, "", "", "12,2|1,3|4,4|12,2|")
End Sub


Function ImportFixedWidth(FileName As String, _
                          StartCell As Range, _
                          IgnoreBlankLines As Boolean, _
                          SkipLinesBeginningWith As String, _
                          SkipLinesBeginningWith2 As String, _
                          ByVal FieldSpecs As String) As Long
    
    
    Const BLOCK_SIZE As Long = 5000   'write this many rows to the sheet once accumulated
    Dim FNum As Integer, s As String
    Dim FieldInfos As Variant, InfoParts As Variant
    Dim allData() As Variant, skip As Boolean, totalLines As Long, numFields As Long
    Dim FileContent As String, Lines, i As Long, n As Long, f As Long, el
    
    Application.EnableCancelKey = xlInterrupt
    On Error GoTo EndOfFunction:
    
    If Dir(FileName, vbNormal) = vbNullString Then ' file not found?
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If Len(FieldSpecs) < 3 Then ' invalid FieldSpecs?
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If StartCell Is Nothing Then
        ImportFixedWidth = -1
        Exit Function
    End If
    
    If StrComp(Right(FieldSpecs, 1), "|", vbBinaryCompare) = 0 Then
        FieldSpecs = Left(FieldSpecs, Len(FieldSpecs) - 1)
    End If
    FieldInfos = Split(FieldSpecs, "|")
    numFields = UBound(FieldInfos) + 1
    
    FNum = FreeFile
    Open FileName For Input Access Read As FNum
    
    totalLines = 0
    ReDim allData(1 To BLOCK_SIZE, 1 To numFields)
    n = 0
    Do
        Line Input #FNum, s
    
        If Len(s) = 0 Then
            If Not IgnoreBlankLines Then n = n + 1 'write blank line?
        Else
            skip = False
            For Each el In Array(SkipLinesBeginningWith, SkipLinesBeginningWith2)
                If Len(el) > 0 Then
                    If InStr(1, s, el, vbTextCompare) = 1 Then
                        skip = True
                        Exit For
                    End If
                End If
            Next el
            If Not skip Then
                n = n + 1
                For f = LBound(FieldInfos) To UBound(FieldInfos)
                    InfoParts = Split(FieldInfos(f), ",")
                    If Len(s) >= CLng(InfoParts(0)) Then
                        allData(n, f + 1) = Mid(s, CLng(InfoParts(0)), CLng(InfoParts(1)))
                    End If
                Next f
            End If  'not skip
        End If      'blank line
        
        'at the limit for the block? If yes then write it out and clear it,
        '  and reset counters etc
        If n = BLOCK_SIZE Then
            totalLines = totalLines + BLOCK_SIZE
            StartCell(1).Resize(BLOCK_SIZE, numFields).Value = allData
            Set StartCell = StartCell.Offset(BLOCK_SIZE)
            ReDim allData(1 To BLOCK_SIZE, 1 To numFields) 'clear array
            n = 0 'reset n
        End If
    Loop Until EOF(FNum)
    
    If n > 0 Then 'write out any remaining lines
        totalLines = totalLines + n
        StartCell(1).Resize(n, numFields).Value = allData
    End If
    
    Close FNum
    
EndOfFunction:
    If Err.Number = 0 Then
        ImportFixedWidth = totalLines
    Else
        Debug.Print Err.Description
        ImportFixedWidth = -1
    End If
End Function

Мне очень жаль, что я так поступил, но единственное, что происходит как ошибка, это то, что если я импортирую очень большой текстовый файл (около 90 000 строк на 300 столбцов), функция приводит к нехватке памяти Excel. Я попробовал пару разных вещей, но все равно не хватает памяти. Могу ли я что-нибудь сделать, если у меня очень большой текстовый файл, чтобы он работал должным образом? В очередной раз благодарим за помощь!

Larcen60 05.06.2024 22:46

Я предполагаю, что одним из подходов может быть чтение файла построчно после изменения размера выходного массива (например) до 5000 строк. Когда массив заполнится, запишите его на лист и увеличьте следующую позицию вставки. Измените размер выходного массива, чтобы очистить его, и продолжайте циклически просматривать файл.

Tim Williams 05.06.2024 23:35

См. отредактированный код выше. Отрегулируйте BLOCK_SIZE по своему усмотрению.

Tim Williams 05.06.2024 23:52

Другие вопросы по теме