При работе с XML-файлами в платформе 1С:Предприятие разработчики часто сталкиваются с задачей, когда необходимо получить «копию» процесса чтения или вернуться к определенному узлу документа, не прерывая текущий сеанс разбора. Попытка скопировать объект ЧтениеXML простым присваиванием новой переменной не дает желаемого результата, так как копируется лишь ссылка на объект, а не состояние самого парсера.
В этой статье мы подробно разберем, почему стандартный объект ЧтениеXML нельзя скопировать «на лету», и рассмотрим эффективные практические альтернативы решения этой задачи в зависимости от объема ваших данных.
Для начала выясним техническую причину этого ограничения. Объект ЧтениеXML в платформе 1С представляет собой обертку над последовательным SAX-парсером (системной библиотекой операционной системы, например, libxml2). Этот парсер спроектирован для максимально быстрой и нетребовательной к памяти работы:
Поскольку объект жестко привязан к физическому потоку операционной системы, платформа 1С не имеет возможности клонировать его внутреннее состояние в произвольной точке чтения. Если вам требуется параллельно работать со вторым таким же набором данных или временно «откатить» чтение назад, придется использовать обходные архитектурные пути.
Если ваша задача заключается в том, чтобы просто прочитать один и тот же файл дважды (например, при первом проходе собрать метаданные или проверить структуру, а при втором — загрузить сами данные), вам не нужно создавать копию объекта в памяти. Достаточно использовать метод сброса указателя.
В отличие от выборок запросов, у объекта ЧтениеXML нет метода Сбросить(). Однако аналогичного поведения можно добиться повторной инициализацией чтения. Рассмотрим этот процесс на примере:
// Создаем объект чтения и открываем файл
ПутьКФайлу = "C:\temp\data.xml";
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу);
// Первый проход: предварительный анализ структуры
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "ВажныйТег" Тогда
// Выполняем предварительные проверки
КонецЕсли;
КонецЦикла;
// Чтобы начать чтение заново, "сбрасываем" курсор переоткрытием
Чтение.Закрыть();
Чтение.ОткрытьФайл(ПутьКФайлу); // Указатель снова в начале файла
// Второй проход: детальная загрузка данных
Пока Чтение.Прочитать() Цикл
// Основной алгоритм разбора данных
КонецЦикла;
Чтение.Закрыть();
Этот способ является наиболее экономичным с точки зрения расхода оперативной памяти, так как в каждый момент времени обрабатывается только одна строка данных.
Если два процесса разбора должны работать полностью независимо и, возможно, параллельно в разных фоновых заданиях, самым простым и надежным решением является физическое дублирование файла-источника на диске.
Для этого мы можем использовать быстрые нативные методы платформы 1С для работы с файловой системой. Проанализируем пример копирования:
ИмяИсходногоФайла = "C:\temp\source.xml";
ИмяФайлаКопии = "C:\temp\source_copy.xml";
// Создаем физическую копию файла на диске
КопироватьФайл(ИмяИсходногоФайла, ИмяФайлаКопии);
// Теперь мы можем открыть два независимых потока чтения
Чтение1 = Новый ЧтениеXML;
Чтение1.ОткрытьФайл(ИмяИсходногоФайла);
Чтение2 = Новый ЧтениеXML;
Чтение2.ОткрытьФайл(ИмяФайлаКопии);
// Далее работаем с каждым объектом параллельно
Преимущества: Полная изоляция потоков чтения. Идеально подходит для многопоточной обработки в фоновом режиме.
Недостатки: Дополнительная нагрузка на дисковую подсистему (I/O) при копировании файлов большого объема.
Что делать, если создавать копии файлов на диске не хочется из-за медленной дисковой подсистемы, а чтение всего XML в обычную строку через ЧтениеТекста вызывает переполнение памяти (Out of Memory)?
В современных версиях платформы 1С:Предприятие 8.3 реализован механизм работы с потоками в оперативной памяти. Мы можем прочитать исходный файл в бинарный буфер (объект ДвоичныеДанные), который оптимизирован платформой, а затем открыть несколько независимых потоков чтения из памяти. Разберем этот паттерн:
ИмяФайла = "C:\temp\bigdata.xml";
// Загружаем файл в бинарный буфер (память выделяется эффективно)
ДанныеФайла = Новый ДвоичныеДанные(ИмяФайла);
// Создаем два независимых потока для чтения данных из одного буфера
Поток1 = Новый ПотокВПамяти(ДанныеФайла);
Поток2 = Новый ПотокВПамяти(ДанныеФайла);
// Инициализируем объекты ЧтениеXML через потоки
Чтение1 = Новый ЧтениеXML;
Чтение1.ОткрытьПоток(Поток1);
Чтение2 = Новый ЧтениеXML;
Чтение2.ОткрытьПоток(Поток2);
// Теперь у нас есть два полноценных и независимых парсера без создания временных файлов
Использование ПотокВПамяти позволяет избежать создания временных файлов на жестком диске и существенно ускоряет работу алгоритмов обработки XML-данных.
Иногда требуется в процессе последовательного чтения XML сразу же создавать его точную копию в другом месте (или отсекать ненужные ветки). Для этого используется связка объектов ЧтениеXML и ЗаписьXML.
Платформа предоставляет оптимизированный метод ЗаписатьТекущий(), который берет на себя всю рутинную работу по переносу структуры тегов, атрибутов и пространств имен. Выясним, как это реализовать:
// Инициализируем чтение исходного файла
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл("C:\temp\source.xml");
// Инициализируем запись нового результирующего файла
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\destination.xml", "UTF-8");
// Запускаем цикл последовательного чтения
Пока ЧтениеXML.Прочитать() Цикл
// Копируем текущий узел "один в один" во второй файл
ЗаписьXML.ЗаписатьТекущий(ЧтениеXML);
КонецЦикла;
// Закрываем потоки
ЗаписьXML.Закрыть();
ЧтениеXML.Закрыть();
Критически важный нюанс: Метод ЗаписатьТекущий() копирует только текущее состояние узла, на котором сейчас расположен указатель парсера ЧтениеXML (например, только открывающий тег). Он не сдвигает указатель чтения автоматически. Чтобы скопировать документ полностью, обязательно нужно вызывать ЧтениеXML.Прочитать() на каждом шаге цикла.
Если ваш XML-документ имеет сравнительно небольшой размер (до нескольких десятков мегабайт), то вместо последовательного чтения SAX целесообразно использовать объектную модель документа — DOM (Document Object Model).
Объекты DOM загружают всю структуру XML в иерархическое дерево в оперативной памяти. У них есть встроенный быстрый метод нативного клонирования, который выполняется без лишних операций ввода-вывода:
// Читаем файл в объектную модель DOM
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл("C:\temp\small_data.xml");
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
ЧтениеXML.Закрыть();
// Создаем полную независимую копию всего дерева документа в памяти
// Параметр "Истина" указывает на необходимость глубокого копирования всех вложенных узлов
КопияДокументаDOM = ДокументDOM.КлонироватьУзел(Истина);
// Теперь у нас есть два независимых объекта DOM, с которыми можно работать параллельно
Этот способ предоставляет максимальное удобство манипулирования данными (поиск по XPath, изменение узлов, перемещение по дереву вверх и вниз), но помните, что для сверхбольших XML-файлов (от 100 МБ и выше) использование DOM может привести к значительному потреблению памяти процессом 1С.
Для удобства выбора оптимального архитектурного решения сопоставим все рассмотренные методы:
| Метод решения | Расход ОЗУ | Нагрузка на диск | Рекомендуемый размер XML | Основной сценарий использования |
|---|---|---|---|---|
| Сброс и переоткрытие (Решение 1) | Минимальный | Низкая (кэш ОС) | Любой (даже гигабайты) | Повторный последовательный анализ одного и того же файла в одном потоке. |
| Физическое копирование файла (Решение 2) | Минимальный | Высокая | До 500 МБ | Параллельная обработка данных независимыми фоновыми заданиями. |
| Потоки в памяти (Решение 3) | Средний | Отсутствует | До 200–300 МБ | Быстрое параллельное чтение без обращения к диску. |
| ЗаписатьТекущий() (Решение 4) | Минимальный | Средняя | Любой | Потоковая фильтрация данных или создание модифицированных копий «на лету». |
| Клонирование DOM (Решение 5) | Высокий | Отсутствует | До 20-50 МБ | Сложная интерактивная работа с иерархической структурой документа в памяти. |