Как узнать количество узлов или сделать индикатор прогресса при чтении большого XML-файла в 1С?

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

При пакетной обработке или интеграции часто приходится сталкиваться с чтением огромных XML-файлов (например, выгрузок данных из «1С:Конвертация данных» объемом от десятков до сотен мегабайт и миллионами узлов). При использовании стандартного объекта ЧтениеXML файл обрабатывается строго последовательно. Узнать общее количество узлов заранее стандартными свойствами парсера невозможно, что усложняет построение привычного индикатора прогресса (прогресс-бара).

Разберем, почему классические подходы с загрузкой документа целиком могут привести к аварийной остановке программы, и рассмотрим надежные практические варианты реализации индикации хода процесса.

Почему объектная модель DOM не подходит для больших файлов

Первая идея, которая часто возникает у разработчиков — использовать ДокументDOM и ПостроительDOM, так как в модели DOM присутствуют штатные методы подсчета и выборки узлов. Однако проанализируем ограничения этого подхода:

  1. Чрезмерное потребление оперативной памяти: при парсинге в DOM структура объектов в оперативной памяти 1С занимает в 5–15 раз больше места, чем сам файл на диске.
  2. Риск аварийного завершения: XML-файл размером 200 МБ, содержащий около 5 миллионов узлов, потребует от 2 до 4 ГБ ОЗУ. Для 32-разрядных процессов сервера 1С или толстого клиента это гарантированно вызовет критическую ошибку «Недостаточно памяти» (Out of memory).

Именно поэтому стандарт 1С рекомендует применять объектную модель DOM только для сравнительно небольших файлов (ориентировочно до 50–100 МБ в зависимости от вложенности и разрядности системы). Для объемных файлов следует применять последовательное или потоковое чтение.

Способ 1. Отслеживание прогресса по смещению байтов через файловый поток

Начиная с версии платформы 8.3.10, объект ЧтениеXML поддерживает инициализацию через потоки данных с помощью метода ОткрытьПоток(). Это позволяет привязать прогресс-бар к физическому объему обработанных данных в байтах, не считывая файл целиком в оперативную память.

Разберем по шагам, как устроен этот механизм:

  1. Открываем файл с помощью объекта ФайловыйПоток в режиме чтения.
  2. Получаем общий вес файла в байтах через метод Размер().
  3. Передаем поток в ЧтениеXML.ОткрытьПоток().
  4. В цикле чтения узлов опрашиваем текущую позицию указателя в потоке через Поток.ТекущаяПозиция().
  5. Вычисляем процент прочитанных данных по формуле: (ТекущаяПозиция / Размер) * 100.

Посмотрим на пример реализации данного алгоритма:


Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
РазмерФайла = Поток.Размер();

ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьПоток(Поток);

ПроцентПредыдущий = 0;

Пока ЧтениеXML.Прочитать() Цикл
    // Обработка данных узла
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        // Выполняем полезную работу
    КонецЕсли;

    // Расчет процента выполнения
    Если РазмерФайла > 0 Тогда
        ТекущийПроцент = Цел((Поток.ТекущаяПозиция() / РазмерФайла) * 100);
        Если ТекущийПроцент > ПроцентПредыдущий Тогда
            ПроцентПредыдущий = ТекущийПроцент;
            // Оповещение интерфейса или фиксация прогресса
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

ЧтениеXML.Закрыть();
Поток.Закрыть();

Так как ЧтениеXML буферизирует чтение с диска, смещение в потоке прирастает блоками, чего вполне достаточно для плавного обновления шкалы выполнения.

Способ 2. Быстрый предварительный проход (Pre-scan)

Если для индикатора принципиально отображать именно количество бизнес-объектов (например, справочников и документов из файла «Конвертации данных»), а не абстрактные байты, эффективным решением будет предварительный проход.

Основное время при загрузке данных (до 95–99%) тратится не на парсинг XML, а на поиск ссылок, проведение проверок и запись объектов в базу данных. «Холостой» первый проход парсером без записи объектов занимает считанные секунды даже на больших объемах.

Рассмотрим порядок действий:

  1. Открываем файл через ЧтениеXML в первом цикле и подсчитываем только открывающие теги ключевых сущностей (например, узлы с именем Объект).
  2. Закрываем файл — теперь нам известно точное максимальное значение для индикатора прогресса.
  3. Открываем файл повторно и запускаем полноценную загрузку данных в базу, сопоставляя счетчик обработанных объектов с полученным максимумом.

Посмотрим на пример предварительного подсчета целевых узлов:


ВсегоОбъектов = 0;

ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ИмяФайла);

// Первый быстрый проход: только подсчет
Пока ЧтениеXML.Прочитать() Цикл
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Объект" Тогда
        ВсегоОбъектов = ВсегоОбъектов + 1;
    КонецЕсли;
КонецЦикла;

ЧтениеXML.Закрыть();

// Второй проход: загрузка с известным общим количеством
ЧтениеXML.ОткрытьФайл(ИмяФайла);
ОбработаноОбъектов = 0;

Пока ЧтениеXML.Прочитать() Цикл
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Объект" Тогда
        // Чтение и запись объекта в базу
        ОбработаноОбъектов = ОбработаноОбъектов + 1;
    КонецЕсли;
КонецЦикла;

ЧтениеXML.Закрыть();

Способ 3. Отображение счетчика без фиксированного максимума

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

В типовых решениях «1С» для аналогичных длительных процедур используется индикатор без конечного значения (анимация ожидания) совместно с текстовым описанием текущего состояния: например, «Обработано объектов: N». Это снимает накладные расходы на предварительный анализ файла и исключает риск зависания системы.

Особенности вывода прогресса в клиент-серверной архитектуре

Реализуя отображение хода загрузки в управляемом приложении, важно помнить об архитектурных ограничениях:

  1. Блокировка клиентского интерфейса: синхронный вызов тяжелой процедуры на сервере «подвешивает» сеанс пользователя. Для корректной индикации длительные операции следует запускать в фоновом задании (например, используя механизм подсистемы «Длительные операции» БСП или объект ФоновыеЗадания).
  2. Оптимизация частоты обновлений: при обработке сотен тысяч и миллионов узлов категорически нельзя отправлять оповещения в форму на каждой итерации цикла. Постоянные вызовы методов регистрации прогресса создадут критическую нагрузку на сервер и сеть. Оповещать клиент следует с шагом не чаще одного раза в 1–3 секунды либо только при изменении целого значения процента.
← На главную