У меня есть функция в электронной таблице 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-файла. Я уверен, что есть простое решение, но есть ли что-то, чего мне не хватает, что могло бы устранить ошибку «Индекс вне диапазона»? Огромное спасибо за помощь!



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
Конечно, попробуйте StartCell.Resize(,numRows).Value = allData.
Спасибо еще раз за помощь! Я попытался обновить его до StartCell.Resize(,numRows).Value = allData, однако это не вставило никаких значений в электронную таблицу. Интересно то, что когда я обновил код до StartCell.Resize(,numRows).Value = Application.Transpose(allData), он вставил первое значение каждого массива горизонтально в первой строке во все столбцы. Есть ли причина, по которой использование только «allData» не вставляет ничего, а «Application.Transpose(allData)» вставляет первое значение из каждого массива в первую строку по столбцам?
Является ли StartCell ячейкой или ячейками?
StartCell определяется как одна ячейка («A5»).
Я загрузил фрагмент. Надеюсь, вам было полезно понять, как записать массив на лист.
Это помогло, большое спасибо! Однако массив по-прежнему не вставляет данные в электронную таблицу, даже после того, как я обновил StartCell до диапазона, а не до отдельной ячейки. Однако, когда я изменил StartCell на диапазон, он, однако, вставил больше строк, когда я выполнил «Application.Transpose(allData)». Если у меня есть массив с большим количеством строк для вставки (90 000), нужно ли мне убедиться, что диапазон охватывает все эти строки? Кажется, если я это сделаю, работа электронной таблицы немного замедлится. Еще раз спасибо!
Уточните, пожалуйста, значение the range encompasses all those rows ? Запись массива (90 тыс. элементов) на лист в моем тесте занимает всего 0,04 секунды. У вас могут быть другие проблемы с производительностью.
Спасибо большое за помощь, выше, твой снипп очень помог с изучением массивов! Ответ Тима ниже работает именно так, как я надеялся!
ОП – к вашему сведению Application.Transpose имеет ограничение (65536) на количество элементов, которые вы можете ему передать. Выше этого возвращаемое значение становится усеченным
Это работает для меня. Данные накапливаются и записываются блоками до тех пор, пока не будет обработан весь файл.
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. Я попробовал пару разных вещей, но все равно не хватает памяти. Могу ли я что-нибудь сделать, если у меня очень большой текстовый файл, чтобы он работал должным образом? В очередной раз благодарим за помощь!
Я предполагаю, что одним из подходов может быть чтение файла построчно после изменения размера выходного массива (например) до 5000 строк. Когда массив заполнится, запишите его на лист и увеличьте следующую позицию вставки. Измените размер выходного массива, чтобы очистить его, и продолжайте циклически просматривать файл.
См. отредактированный код выше. Отрегулируйте BLOCK_SIZE по своему усмотрению.
Спасибо за помощь, я ценю это! Это определенно на правильном пути, однако приведенное выше предложение переносит мои данные вертикально, а не горизонтально. Кроме того, он также помещает только первый набор данных вместо всех наборов данных. Есть ли способ вставить данные так, чтобы они были горизонтальными, а не вертикальными?