Прочитать текстовый файл в кодировке UTF-8 BOM

Автор timal1234, 14 ноября 2025, 19:52

0 Пользователи и 1 гость просматривают эту тему.

timal1234

Всем привет!

Подскажите, как прочитать текстовый файл в кодировке UTF-8 BOM и записать его в массив.
в файле БОЛЕЕ 1 млн. строк!

Через
filenumber = Freefile
lineNumber = 1 '  с 1-ой строчки
Open file For Input As filenumber ' указывает, что мы используем это как вход для подпрограммы, а не то, что мы собираемся в неё что-то передавать
While not EOF(filenumber) ' пока не достигнут конец файла
Line Input #filenumber, iLine 'начинаем чтение файла  с 1-ой строчки и сохраняем текст строки в переменную iLine
If iLine <> "" then ' если не пустая строка в файле
Full_LOG(lineNumber - 1) =  iLine ' записываем в массив содержимое строки
lineNumber = lineNumber + 1 ' следующая линия
end if
wend
Close #filenumber
считывает быстро, но в ANSII.

Через
Dim Full_LOG(0 to 0)  As String ' массив текущего файла
Dim filePath
filePath = ConvertToUrl(file)

Dim oSFA As Object
oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")

IF NOT oSFA.exists(filePath) THEN
  MsgBox("!!!  НЕТ такого файла  !!!!  " + CHR(13) + "выход из макроса " , 0+0+48)
  Exit Sub
END IF

Dim oUcb, oInputStream, sInputLine$
oUcb = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
oInputStream = CreateUnoService("com.sun.star.io.TextInputStream")
oInputStream.setInputStream(oUcb.openFileRead(filePath))
oInputStream.setEncoding("UTF-8") 'UTF-8 format
lineNumber = 1
Do While Not oInputStream.isEOF()
Full_LOG(lineNumber - 1) =  oInputStream.readLine() ' записываем в массив  содержимое строки
ReDim Preserve Full_LOG(0 to lineNumber) As String
lineNumber = lineNumber + 1 ' следующая линия
Loop
oInputStream.closeInput()
не могу дождаться окончания (20 минут ждал и не дождался)

Что не так?
почему так долго длится чтение?
-----------
если не переопределять массив, а задать ему размер по количеству строк в файле Dim Full_LOG(0 to lineCount - 1)  As String, то ругается на Full_LOG(lineNumber - 1) =  oInputStream.readLine(), что индекс за перелами диапазона

bigor

Цитата: timal1234 от 14 ноября 2025, 19:52задать ему размер по количеству строк в файле
а как вы получаете lineCount ?
И для чего это все? если файл больше миллиона строк, то при одном символе в строке это минимум 2 Mb
Поддержать наш форум можно здесь

timal1234

Цитата: bigor от 14 ноября 2025, 23:25
Цитата: timal1234 от 14 ноября 2025, 19:52задать ему размер по количеству строк в файле
а как вы получаете lineCount ?
вот так:
'Get line count
lineCount = 0
filenumber = Freefile
Open file For Input As filenumber 'указывает , что мы используем это как вход для подпрограммы, а не то, что мы собираемся в неё что-то передавать

'Используйте EOF, чтобы избежать ошибок при попытке получить данные за концом файла.
'При использовании инструкции Input или Get для считывания из файла указатель позиции в файле продвигается на количество считанных байтов.
'Если конец файла достигнут, EOF возвращает значение "True" (-1).

While not EOF(filenumber) ' пока не достигнут конец файла
Line Input #filenumber, iLine 'cчитать текущую строку файла и сохранять в переменную ' iLine
If iLine <> "" then 'если не пустая строка в файле
lineCount = lineCount + 1 ' следующая строка (идём дальше вниз)
end if
wend
Close #filenumber

ЦитироватьИ для чего это все?
задача:
найти в лог-файле строки, удовлетворяющие условию и записать на лист (ну или в новый файл).
лог-файл содержит пути к файлам. и если считывать по первому способу, русские символы превращаются в крякозябры.
уже думаю, может проще макросом удалить из этого файла ненужные строки.

Цитироватьесли файл больше миллиона строк, то при одном символе в строке это минимум 2 Mb
вопрос не понял... да, файл большой - 200Мб

mikekaganski

#3
Цитата: timal1234 от 14 ноября 2025, 19:52почему так долго длится чтение?
А с чего Вы взяли, что это чтение?
Вы сами указали на переопределение массива. Правда, пришлось задавать дополнительные вопросы, где выяснилось, что Ваш метод подсчёта числа строк даст 0 для файла с одной пустой строкой и без перевода строки. Да и вообще любые пустые строки он будет игнорировать.

А главное: если результат нужен в другом файле или в документе, то зачем вообще промежуточный массив? Сразу туда и пишите.
С уважением,
Михаил Каганский

timal1234

Цитата: mikekaganski от 15 ноября 2025, 11:28А главное: если результат нужен в другом файле или в документе, то зачем вообще промежуточный массив? Сразу туда и пишите.
Получилось !
Спасибо огромное!

timal1234

получилось вот так:

Dim filePath
filePath = ConvertToUrl(file)

Dim x
x = 0

Dim OFFSET
OFFSET = 2

Dim oSFA As Object
oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")

IF NOT oSFA.exists(filePath) THEN
  MsgBox("!!!  НЕТ такого файла  !!!!  " + CHR(13) + "выход из макроса " , 0+0+48)
ThisComponent.Sheets.getByName("Инфицированные").getCellByPosition(1, 0).setString( "!!! ПРЕРВАНО !!!" )
  Exit Sub
END IF

Dim Curr_STR As String
Dim oUcb, oInputStream, sInputLine$
oUcb = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
oInputStream = CreateUnoService("com.sun.star.io.TextInputStream")
oInputStream.setInputStream(oUcb.openFileRead(filePath))
oInputStream.setEncoding("UTF-8") 'UTF-8 format
Do While Not oInputStream.isEOF()
Curr_STR =  oInputStream.readLine() ' записываем в переменную содержимое строки

IF InStr(Curr_STR, "Обучение") > 0 THEN
ThisComponent.Sheets.getByName("Результат").getCellByPosition(0, x+OFFSET).setString( Curr_STR )
x = x+ 1
END IF
Loop
oInputStream.closeInput()

bigor

Цитата: timal1234 от 15 ноября 2025, 10:29строки, удовлетворяющие условию
Почему же сразу при чтении строки не проверять ее на условие и записывать в массив только нужные. Вижу вы пошли по этому пути :)
Поддержать наш форум можно здесь

timal1234

Цитата: bigor от 15 ноября 2025, 12:34
Цитата: timal1234 от 15 ноября 2025, 10:29строки, удовлетворяющие условию
Почему же сразу при чтении строки не проверять ее на условие и записывать в массив только нужные. Вижу вы пошли по этому пути :)

просто у меня в заготовках были 2 варианта чтения файла...
первый как оказалось не работает с UTF-8.
второй (как он был изначально) не сработал вообще ....
я думал сначала надо файл загнать в массив, а потом что-то искать  ;D

ВСЕМ СПАСИБО !

economist

Лет десять назад люди уже отточили навык брать, отбирать, сохранять данные самым простым и доступным способом (язык Python входит в состав LibreOffice и он теперь везде). Миллиарды строк - не проблема. Отбор строк возможен как угодно (с/без регистра, по списку, регуляркой итд). Но главное - простота кода:

import pandas as pd
df = pd.read_csv('C:/Temp/1.txt', sep=';')         # лучший sep \t табуляция, как в 1С-файлах
df = df.query(" Вид.str.startswith('Обучение') ")  # отобрали нужн строки
df.to_excel('C:/Temp/1.xlsx')                      # сохранили

Сам же TXT файл обычно выглядит так и м.б. в любой кодировке:
Вид;Балл
Обучение;1
Экзамен;0

Добавив пару даже не строк, а несколько слов(!) - вы получите какой угодно по сложности агрегированный отчет, со средними баллами, когортами итд. Для Calc остается почетная миссия открыть готовый файл и раскрасить его (Basic/VBA-макросом, в стиле [A1].Font.Bold = True). Использование Python с Calc несет удивительно продуктивное разделение труда.   
Пить не буду коньяка - читану Питоньяка!

timal1234


economist

В LibreOffice на Basic можно запустить py-файл из примера выше
- встроенным интерпретатором Python методом invoke(), есть примеры на Форуме
- внешним python функцией shell(), есть примеры на Форуме

Да, это усложнение, но еще и ускорение (особенно в фильтрации данных).
А на миллиардах строк - то немногое что вообще справится с задачей. 
Пить не буду коньяка - читану Питоньяка!

sokol92

Спасибо автору и участникам за интересную тему, имеющую практическое значение.

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

Необходимые макросы находятся в прилагаемом файле (текст макросов см. ниже).

Стартовые действия:

1. Заменить в Module1 строку (если необходимо)
Const filePath as String = "C:\Temp\testfile.txt"на путь к тестовому файлу (будет создан в следующем пункте).

2. Запустить макрос CreateTestFile, который создаст тестовый файл в кодировке UTF-8 (по образцу стартового сообщения).
Число записей - 1 000 000, размер файла 139 Mb, каждая сотая запись содержит строку "Обучение".
Время выполнения макроса (у меня) - около 10 сек.

Цель создаваемой программы - отфильтровать строки, содержащие подстроку "обучение" (регистронезависимо), и поместить в первый столбец листа электронной таблицы.

Для запуска теста используется макрос TestMain, который создает новую таблицу Calc, вызывает функцию FileToSheet (тело которой в примере реализовано по образцу макроса автора этой темы) и показывает время выполнения.
Время выполнения (у меня) - 35 сек.

Задача практикума - заменить тело функции FileToSheet с целью оптимизации времени выполнения.
Дополнительное условие:
Решение должно работать в текущей верcии LibreOffice (25.2.7, 25.8.3) в стандартной установке (не требовать установки дополнительных компонентов / расширений).

Если автор темы не возражает, то решение можно выкладывать здесь с указанием времени выполнения (желательно прилагать также файл с текстом программы).
Option Explicit

Const filePath as String = "C:\Temp\testfile.txt"


' Запускает тест.
Sub TestMain()
  Dim oDoc as Object
  Dim t as Long, retval as Long
  oDoc = StarDesktop.LoadComponentFromUrl("private:factory/scalc", "_default", 0, Array())
  t = GetSystemTicks()
 
  retval = FileToSheet(ConvertToUrl(filePath), "обучение", oDoc.Sheets(0))   
 
  Msgbox "Функция вернула значение " & retval & Chr(10) & _
         "Время выполнения (мс) " & (GetSystemTicks() - t)
End Sub


' Читает текстовый файл fileURL в кодировке UTF-8 и записывает строки, содержащие textToFind,
' в первый столбец листа oSheet.
' Возвращает число записанных строк.
Function FileToSheet(Byval fileURL as String, Byval textToFind as String, Byval oSheet as Object) as Long
  Dim x
  x = 0
 
  Dim oSFA As Object
  oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")

  Dim Curr_STR As String
  Dim oUcb, oInputStream, sInputLine$
  oUcb = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
  oInputStream = CreateUnoService("com.sun.star.io.TextInputStream")
  oInputStream.setInputStream(oUcb.openFileRead(filePath))
  oInputStream.setEncoding("UTF-8") 'UTF-8 format
  Do While Not oInputStream.isEOF()
Curr_STR =  oInputStream.readLine() ' записываем в переменную содержимое строки
If InStr(Curr_STR, textToFind) > 0 Then
  oSheet.getCellByPosition(0, x).setString( Curr_STR )
  x = x+ 1
End If
  Loop
  oInputStream.closeInput()
 
  FileToSheet = x
End Function


' Создает текстовый файл.
Sub CreateTestFile()
  Dim arr, i as Long, t as Long
  Dim nRows as Long  ' число записей
 
  nRows = 1000000
 
  t = GetSystemTicks()
  Redim arr(1 to nrows)
  For i = 1 To nrows
    arr(i) = CStr(i) & IIf(i Mod 100 = 0, _
      " Обучение оптимальному чтению текстовых файлов и записи на лист электронной таблицы", _
      " Произвольный текст, который не будет включен в результирующий набор данных")   
  Next i
 
  StrToFile Join(arr, Chr(10)), filePath   
  Msgbox "Файл для тестирования создан: " & filePath & Chr(10) & _
         "Время создания (мс) " & (GetSystemTicks() - t)
End Sub


' ------------------------
' Вспомогательные макросы
' ------------------------

' Записывает строку str в файл в кодировке UTF-8
Sub StrToFile(Byval str As String, Byval filePath As String)
  Dim oTextStream as Object, oSFA as Object
  oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
  oTextStream = CreateUnoService("com.sun.star.io.TextOutputStream")
  With oTextStream
    .setOutputStream oSFA.openFileWrite(ConvertToURL(filePath))
    .writeString str
    .closeOutput()
  End With 
End Sub

Владимир.

timal1234


economist

#13
Ну, вызов принят! Теперь на готовом файле покажем скорость Python (см. темный скрин):
Python - чтение TXT и запись выборки ы XLSX.png

import pandas as pd
df = pd.read_csv('C:/Temp/testfile.txt', dtype=str, header=None, names=['поле1'], sep=';')       
df = df.query(" поле1.str.contains('Обучение') ")  # отобрали нужн строки
df.to_excel('C:/Temp/out.xlsx')                      # сохранили

То есть Python прочитал 139MB файл, выбрал строки по подстроке (~10k) и записал их в файл XLSX (в ODS то же самое) за 1,8 секунды. Это заметно быстрее чем Calc/Basic.

PS: Спасибо @sokol92 за код генерации файла и саму идею теста.
Пить не буду коньяка - читану Питоньяка!

sokol92

Цитата: economist от  3 декабря 2025, 14:01import pandas
Прекрасный результат, но получен с нарушением дополнительного условия, поскольку требуется пакет (pandas), который не входит в стандартную установку. Те не менее, будем иметь в виду.  :)

Пока попробуем выжать соки из LO Basic.
Одна из основных причин медленной работы Basic (как и VBA) - частое общение с "внешним миром". В данном случае мы для каждой найденной строки вызываем метод UNO, который записывает это значение в ячейку. Мы вправе рассчитывать на ускорение, если накопим найденные значения в массиве, а затем за один вызов перенесем на лист.
Поскольку мы заранее не знаем размер массива, то будем использовать хорошо известную стратегию удвоения.

В начале отведем массиву большое (но не слишком) число элементов. При полном заполнении массива удвоим число его элементов (с сохранением ранее накопленных).
Итак, вот что получается.
Время выполнения (у меня) - 21 сек.

С учетом информации от коллеги @economist понятно, что будем двигаться дальше.
' Читает текстовый файл fileURL в кодировке UTF-8 и записывает строки, содержащие textToFind,
' в первый столбец листа oSheet.
' Возвращает число записанных строк.
Function FileToSheet(Byval fileURL as String, Byval textToFind as String, Byval oSheet as Object) as Long
  Dim arr, indArr as Long, ub as Long, s as String
  Dim oSFA as Object, oInputStream as Object
 
  ub = 999      ' стартовое значение для верхней границы массива arr
  Redim arr(ub) 
  indArr = -1   ' индекс последнего заполненного элемента arr
 
  oSFA = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
  oInputStream = CreateUnoService("com.sun.star.io.TextInputStream")
  oInputStream.setInputStream(oSFA.openFileRead(fileURL))
 
  Do While Not oInputStream.isEOF()
s =  oInputStream.readLine() ' записываем в переменную содержимое строки
If InStr(s, textToFind) > 0 Then
  indArr = indArr + 1
 
  If indArr > ub Then
     ub = ub * 2
     Redim Preserve arr(ub)
  End If

  arr(indArr) = Array(s)
End If
  Loop
  oInputStream.closeInput()
 
  If indArr>=0 Then
    Redim Preserve arr(indArr)
    oSheet.getCellRangeByPosition(0, 0, 0, UBound(arr)).DataArray = arr
  End If 
 
  FileToSheet = indArr + 1
End Function

Владимир.