Как ускорить разбор текстовых файлов и извлечение Base64-данных в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с файлами в 1С, особенно если это выгрузки почтовых сообщений или логов, разработчики часто сталкиваются с проблемой катастрофического падения производительности. В этой статье мы разберем реальный кейс, когда чтение небольшого файла размером 6 МБ занимало более 3 минут, и выясним, как сократить это время до долей секунды. Мы проанализируем типичные ошибки и рассмотрим несколько профессиональных подходов к решению задачи.

Постановка задачи и анализ проблемы

Представим ситуацию: у нас есть текстовый файл (структура MIME/EML), содержащий вложение в формате PDF, закодированное в Base64. Нам необходимо программно найти начало блока данных, собрать строку Base64 и преобразовать её в объект ДвоичныеДанные. Рассмотрим исходный код, который привел к задержкам:


ЧтениеТекста = Новый ЧтениеТекста(Файл.ПолноеИмя, КодировкаТекста.ANSI);
ТекущаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
Пока ТекущаяСтрока <> Неопределено Цикл 
    ТекущаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
    Если СтрНачинаетсяС(ТекущаяСтрока, "Content-Transfer-Encoding: base64") Тогда
        СтрокаБазе64 = "";
        ТекущаяСтрокаДД = ЧтениеТекста.ПрочитатьСтроку();
        Пока ТекущаяСтрокаДД <> Неопределено Цикл 
            ТекущаяСтрокаДД = ЧтениеТекста.ПрочитатьСтроку();
            Если СтрНачинаетсяС(ТекущаяСтрокаДД, "--") Тогда
                Прервать;
            КонецЕсли;    
            // ГЛАВНАЯ ОШИБКА ЗДЕСЬ:
            СтрокаБазе64 = СтрокаБазе64 + ТекущаяСтрокаДД; 
        КонецЦикла;
        МассиДД.Добавить(Base64Значение(СтрокаБазе64));
    КонецЕсли;
КонецЦикла;

В чем причина низкой скорости? Проблема заключается в строке СтрокаБазе64 = СтрокаБазе64 + ТекущаяСтрокаДД. В платформе 1С (как и во многих других языках программирования) строки являются неизменяемыми (immutable) объектами. Это означает, что при каждой операции сложения система не просто добавляет символы в конец, а создает в оперативной памяти новую строку, копируя в неё содержимое старой и добавляя новую порцию данных.

Для файла в 6 МБ, который читается построчно (например, по 76 символов в строке Base64), выполняются десятки тысяч операций перевыделения памяти и копирования все возрастающих объемов данных. К концу процесса каждое добавление новой строки заставляет систему копировать мегабайты данных в новое место памяти, что и вызывает "зависание" на несколько минут.

Решение №1: Использование массива и метода СтрСоединить

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

Разберем этот метод по шагам:

  1. Создаем объект Массив для временного хранения строк.
  2. В цикле чтения вместо конкатенации используем метод Добавить().
  3. После завершения цикла чтения блока данных вызываем функцию СтрСоединить(МассивРезультат, "").

Этот подход работает значительно быстрее, так как 1С при выполнении СтрСоединить заранее вычисляет необходимый объем памяти для итоговой строки и выполняет копирование данных всего один раз.

Решение №2: Использование объектов ЧтениеДанных и БуферДвоичныхДанных

Если мы хотим добиться максимальной производительности, нам следует отказаться от высокоуровневого объекта ЧтениеТекста в пользу работы с двоичными данными. Начиная с версии платформы 8.3.9, доступны объекты ЧтениеДанных и БуферДвоичныхДанных.

Рассмотрим, почему это эффективнее:

Пример логики работы с буфером:


ФайлПоток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть);
Чтение = Новый ЧтениеДанных(ФайлПоток);
// Ищем маркеры начала и конца блока через методы поиска в буфере
// Используем ПолучитьБуферДвоичныхДанныхИзBase64БуфераДвоичныхДанных()

Такой подход позволяет избежать создания гигантских строк в памяти и работает на уровне скорости дисковой подсистемы.

Решение №3: Запись во временный файл

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

Алгоритм действий следующий:

  1. Создаем временный файл через ПолучитьИмяВременногоФайла().
  2. Открываем его на запись через ЗаписьТекста.
  3. При разборе исходного файла записываем каждую строку Base64 во временный файл без разделителей.
  4. После окончания разбора блока закрываем временный файл и считываем его целиком в одну строку или сразу в ДвоичныеДанные.

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

Решение №4: Встроенные механизмы разбора почты

Проанализируем структуру файла из примера. Мы видим заголовки Content-Type: application/pdf и Content-Transfer-Encoding: base64. Это стандартный формат электронного письма (MIME). В 1С есть мощный встроенный инструмент для работы с такими файлами — объект ИнтернетПочтовоеСообщение.

Вместо того чтобы изобретать собственный парсер, мы можем сделать следующее:


Сообщение = Новый ИнтернетПочтовоеСообщение;
Сообщение.Прочитать(ПутьКФайлу); 
// Все вложения уже извлечены и находятся в коллекции Сообщение.Вложения
Для Каждого Вложение Из Сообщение.Вложения Цикл
    ДвоичныеДанныеФайла = Вложение.Данные;
КонецЦикла;

Преимущества этого способа: 1. Надежность: Платформа сама корректно обработает сложные случаи (разные кодировки заголовков, вложенные части сообщения, разные типы разделителей). 2. Скорость: Разбор MIME-структуры реализован на уровне ядра платформы (C++), что всегда быстрее кода на встроенном языке. 3. Чистота кода: Вам не нужно писать циклы поиска строк и следить за памятью.

Выводы и рекомендации

Выясним, какой метод выбрать в зависимости от ситуации:

Применяя эти правила, вы сможете ускорить обработку файлов в десятки и сотни раз, обеспечивая комфортную работу пользователей с системой 1С.

← На главную