Как прочитать большой XML файл в 1С без ошибки "Недостаточно памяти"?

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

При интеграции 1С с другими системами мы часто сталкиваемся с необходимостью загрузки данных из формата XML. Это могут быть прайс-листы поставщиков, данные о продажах с сайта или обмен документами с контрагентами. Пока файлы небольшие, проблем обычно не возникает. Однако, как только размер файла вырастает до сотен мегабайт или гигабайтов, стандартные методы чтения могут приводить к критической ошибке платформы — "Недостаточно памяти" (Out of memory).

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

Почему возникает ошибка памяти при использовании Построителя DOM?

Чаще всего при начале работы с XML разработчики используют объект ПостроительDOM. Это удобный инструмент, который позволяет обращаться к узлам XML как к дереву объектов, использовать XPath-запросы и легко навигировать по структуре документа.

Однако у этого удобства есть высокая цена. Рассмотрим, как работает ПостроительDOM: он пытается прочитать весь файл целиком и построить в оперативной памяти полную структуру документа (объектную модель). Если файл весит, например, 500 Мб, то для построения его DOM-модели в памяти может потребоваться в 5–10 раз больше места (2–5 Гб ОЗУ). В условиях 32-битного клиента или сервера, где адресное пространство ограничено 2-3 Гб, это неизбежно приводит к аварийному завершению работы.

Даже на 64-битных серверах такой подход крайне неэффективен: мы "забиваем" память сервера, мешая работе других пользователей, и тратим много времени на построение дерева, которое, возможно, нам нужно прочитать всего один раз.

Решение: Использование последовательного чтения

Чтобы избежать переполнения памяти, перейдем к использованию объекта ЧтениеXML. Этот метод работает принципиально иначе: он не загружает файл в память целиком. Вместо этого мы последовательно, узел за узлом, перемещаемся по файлу от начала к концу. Как только мы прочитали данные одного узла и перешли к следующему, память, занимаемая предыдущим узлом, может быть освобождена.

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

Алгоритм реализации последовательного чтения

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

  1. Инициализация объекта чтения.
  2. Открытие файла.
  3. Циклический обход узлов.
  4. Анализ типа текущего узла и чтение данных.
  5. Завершение работы и закрытие файла.

Создадим новый объект ЧтениеXML и настроим параметры открытия файла. Нам важно корректно указать путь к файлу. Если файл находится на клиенте, его предварительно нужно передать на сервер (например, во временное хранилище), но для примера предположим, что файл уже доступен серверу по локальному пути.


// Создаем объект чтения
Чтение = Новый ЧтениеXML;

// Настраиваем параметры чтения, если необходимо (например, игнорировать пробелы)
ПараметрыЧтения = Новый ПараметрыЧтенияXML(,,,, Истина); 

// Открываем файл
// Важно: ПутьКФайлу должен быть доступен процессу сервера
Чтение.ОткрытьФайл(ПутьКФайлу, ПараметрыЧтения);

Организация цикла чтения

Теперь перейдем к самому главному — циклу обхода. Метод Прочитать() сдвигает "курсор" на следующий узел. Он возвращает Истина, если чтение прошло успешно, и Ложь, если файл закончился. Это идеальное условие для цикла Пока.

Внутри цикла мы должны анализировать свойство ТипУзла. XML содержит разные типы узлов: начало элемента (открывающий тег), конец элемента (закрывающий тег), текст (содержимое тега), комментарии и другие.

Посмотрим на структуру кода для обработки:


Пока Чтение.Прочитать() Цикл
    
    // Проверяем, является ли текущий узел началом элемента (открывающим тегом)
    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        // Получаем имя текущего тега
        ИмяУзла = Чтение.Имя;
        
        // Предположим, мы ищем данные о товарах в теге "Product"
        Если ИмяУзла = "Product" Тогда
            // Здесь мы будем обрабатывать данные одного товара
            ОбработатьДанныеТовара(Чтение);
        КонецЕсли;
        
    КонецЕсли;

КонецЦикла;

// Не забываем закрыть файл после завершения работы
Чтение.Закрыть();

Обратите внимание, что мы выделили обработку конкретного элемента в отдельную процедуру ОбработатьДанныеТовара. Это хорошая практика, позволяющая сделать код модульным и читаемым.

Получение атрибутов и вложенных значений

При последовательном чтении данные могут храниться двумя способами: в атрибутах тега (например, <Product id="123">) или во вложенных текстовых узлах (например, <Name>Товар</Name>).

Вариант 1: Чтение атрибутов

Атрибуты доступны сразу, когда мы находимся на узле НачалоЭлемента. Нам не нужно двигаться дальше. Используем метод ПолучитьАтрибут().


// Внутри условия Если ИмяУзла = "Product" ...
Артикул = Чтение.ПолучитьАтрибут("Code");
Цена = Чтение.ПолучитьАтрибут("Price");

Вариант 2: Чтение вложенных тегов

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

Рассмотрим более детально, как извлечь текст из тега <Name>Телевизор</Name>. После того как мы поймали НачалоЭлемента с именем "Name", следующий вызов Прочитать() переведет нас на узел типа Текст, содержащий строку "Телевизор".


Если Чтение.Имя = "Name" И Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
    
    // Сдвигаемся к содержимому
    Чтение.Прочитать(); 
    
    Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
        НаименованиеТовара = Чтение.Значение;
    КонецЕсли;
    
КонецЕсли;

Комбинированный подход: ЧтениеXML + XDTO

Если структура внутри повторяющихся элементов (например, внутри <Product>) достаточно сложная, писать полностью ручной парсер утомительно. Мы можем скомбинировать подходы. Будем использовать ЧтениеXML, чтобы добраться до нужного узла, а затем прочитаем только этот кусочек через Фабрику XDTO.

Это очень мощный прием: мы не грузим весь файл в память, но работаем с данными объекта удобно, как со структурой.


Пока Чтение.Прочитать() Цикл
    
    Если Чтение.Имя = "Product" И Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        // Читаем текущий фрагмент XML как объект XDTO
        // Платформа прочитает всё содержимое тега Product и вернет объект
        Попытка
            ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение);
            
            // Теперь обращаемся к полям через точку, как к свойствам
            НоваяНоменклатура = Справочники.Номенклатура.СоздатьЭлемент();
            НоваяНоменклатура.Наименование = ОбъектXDTO.Name;
            НоваяНоменклатура.Артикул = ОбъектXDTO.Code;
            // ... запись объекта ...
            
        Исключение
            // Обработка ошибок чтения конкретного блока
            ЗаписьЖурналаРегистрации("ЗагрузкаXML", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
        КонецПопытки;
        
    КонецЕсли;
    
КонецЦикла;

Этот метод (гибридный) является "золотой серединой". Он экономит память (в памяти находится только один объект Product) и значительно упрощает код, избавляя нас от десятков проверок типов узлов.

Важные рекомендации по производительности

При работе с большими объемами данных (тысячи и миллионы элементов) просто прочитать файл недостаточно. Чтобы найти причину замедлений, необходимо оптимизировать и сам процесс записи данных в базу 1С.

  1. Транзакции: Не пытайтесь записать все элементы в одной транзакции. Если файл огромный, транзакция разрастется и заблокирует таблицы или переполнит журнал транзакций SQL. Разбивайте загрузку на порции (например, записывайте по 100-500 элементов, затем фиксируйте транзакцию и начинайте новую).
  2. Поиск ссылок: Минимизируйте запросы в цикле. Если возможно, используйте кэширование (например, соответствие артикулов и ссылок) в переменной Соответствие, но следите, чтобы она не разрослась слишком сильно.
  3. Отключение итогов: Если вы загружаете движения в регистры накопления, рассмотрите возможность временного отключения итогов на время загрузки.

Резюме

Для решения проблемы с нехваткой памяти при чтении XML в 1С:

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

← На главную