При работе с внешними данными, такими как выгрузки проверок государственных органов, мы часто сталкиваемся с XML-файлами огромного объема (от 1 ГБ до 10 ГБ и выше). Попытка прочитать такой файл целиком через ФабрикаXDTO или ПостроительDOM неизбежно приводит к критическому потреблению оперативной памяти и падению рабочего процесса сервера 1С. Разберем наиболее эффективные стратегии обработки таких файлов, когда нам нужно найти информацию по конкретному списку ИНН.
Основная проблема начинающих разработчиков — использование метода ФабрикаXDTO.ПрочитатьXML(ЧтениеXML) сразу для всего файла. В этом случае система пытается построить в памяти полное дерево объектов, что для файла в 2 ГБ может потребовать более 10–15 ГБ ОЗУ. Правильный подход заключается в использовании объекта ЧтениеXML как последовательного сканера (SAX-подобный парсинг).
Рассмотрим алгоритм: мы двигаемся по файлу узел за узлом, не загружая всё дерево в память. Проанализируем пример реализации:
СписокИНН = Новый Соответствие; // Используем Соответствие для максимально быстрого поиска
Для Каждого ИННИзСписка Из МоиИНН Цикл
СписокИНН.Вставить(ИННИзСписка, Истина);
КонецЦикла;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
Пока ЧтениеXML.Прочитать() Цикл
// Ищем начало узла, описывающего проверку (например, "Inspection")
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Inspection" Тогда
// Читаем атрибуты или вложенные свойства, чтобы найти ИНН
ИННПроверки = ЧтениеXML.ПолучитьАтрибут("INN");
Если СписокИНН.Получить(ИННПроверки) <> Неопределено Тогда
// ИНН найден в нашем списке, обрабатываем данные
ОбработатьДанныеПроверки(ЧтениеXML);
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Важный нюанс: для поиска по списку ИНН используем именно объект Соответствие. Поиск в нем выполняется за константное время O(1), в отличие от ТаблицаЗначений.Найти() или поиска в массиве, что критично при миллионах итераций цикла.
Если структура узла внутри XML сложная (много вложенных уровней, адресов, контактов), разбирать ее вручную через ЧтениеXML.Прочитать() очень трудоемко. Мы можем применить "гибридный" метод: последовательно дойти до нужного узла, а затем скормить только этот фрагмент ФабрикеXDTO.
Выясним, как это работает: метод ФабрикаXDTO.ПрочитатьXML() может принимать вторым параметром тип объекта. Если мы передадим туда открытый объект ЧтениеXML, который позиционирован на начале узла, фабрика прочитает только этот узел и остановится. Память при этом расходуется только на одну небольшую структуру.
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Inspection" Тогда
// Проверяем ИНН (допустим, он в атрибуте)
Если МойСписокИНН.Содержит(ЧтениеXML.ПолучитьАтрибут("INN")) Тогда
// Читаем ТОЛЬКО этот узел в объект XDTO
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, ФабрикаXDTO.Тип("http://schema.link", "InspectionType"));
// Теперь у нас есть удобный объект, к полям которого можно обращаться через точку
НоваяЗапись = Справочники.Проверки.СоздатьЭлемент();
ЗаполнитьЗначенияСвойств(НоваяЗапись, ОбъектXDTO);
НоваяЗапись.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
Этот метод сочетает в себе удобство типизации XDTO и быстродействие потокового чтения.
Если нам нужно обработать 12 файлов по 2 ГБ, а наши 100 ИНН могут встретиться всего в паре из них, имеет смысл сначала проверить наличие ИНН в файле как в обычном тексте, не запуская тяжелый парсинг XML.
Проанализируем ситуацию: объект ЧтениеТекста позволяет читать файл блоками. Мы можем искать подстроку ИНН в текстовом потоке. Однако стоит учитывать, что в XML ИНН может быть разбит на части или экранирован (хотя для цифр это редкость). Самый простой и быстрый способ для первичного отсева файлов — использование метода СтрНайти в блоке текста.
Функция ФайлСодержитИНН(ПутьКФайлу, ИНН)
Текст = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.UTF8);
// Читаем файл порциями по 1 Мб, чтобы не перегружать ОЗУ
Пока Истина Цикл
Фрагмент = Текст.Прочитать(1000000);
Если Фрагмент = Неопределено Тогда Прервать; КонецЕсли;
Если СтрНайти(Фрагмент, ИНН) > 0 Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Внимание: Если XML записан в одну строку (без переносов \n), метод ПрочитатьСтроку() может попытаться загрузить весь гигабайтный файл в одну переменную. Всегда используйте Прочитать(КоличествоСимволов) для работы с большими данными.
Если файлов много, чтение каждого по очереди займет часы. Платформа 1С позволяет эффективно распараллелить эту задачу. Создадим массив путей к файлам и запустим для каждого файла отдельное фоновое задание.
Разберем этапы реализации:
ФоновыеЗадания.Выполнить(), передавая путь к файлу и список ИНН.При использовании фоновых заданий на сервере с 12 ядрами и SSD-накопителем, общее время обработки сокращается практически пропорционально количеству ядер, так как чтение XML — это операция, сильно нагружающая одно ядро процессора.
В некоторых случаях, когда производительность 1С на чтение огромных текстовых массивов недостаточна, можно прибегнуть к помощи внешних инструментов. Например, консольная утилита grep (доступна и в Windows через Git Bash или WSL) способна найти строки с нужными ИНН в многогигабайтном файле за считанные секунды. Результат работы утилиты можно сохранить в промежуточный файл и прочитать его из 1С.
Рассмотрим пример вызова:
Команда = "grep -F -f list_inn.txt big_data.xml > filtered_nodes.xml";
ЗапуститьПриложение(Команда,, Истина);
Здесь list_inn.txt — файл со списком ваших ИНН, а filtered_nodes.xml — результат, содержащий только строки с совпадениями. После этого 1С останется обработать лишь крошечный фрагмент данных.
Подведем итог нашему анализу:
ПостроительDOM и полной загрузке через ФабрикаXDTO.ЧтениеXML для перебора узлов верхнего уровня.Соответствие для мгновенного поиска ИНН в памяти.ЧтениеXML с частичным вызовом ФабрикаXDTO.ПрочитатьXML() для обработки сложных структур внутри узла.Out of Memory при работе со строковыми буферами.Следуя этим правилам, вы сможете обрабатывать данные любого объема, ограничиваясь лишь скоростью работы вашей дисковой подсистемы.