При разработке обработок загрузки данных из электронных таблиц часто возникает вопрос архитектурного выбора: где производить чтение и первичную обработку файла — в контексте клиента или на сервере? Особенно остро этот вопрос встает, когда требуется предварительно подготовить данные, показать их пользователю на форме для согласования и лишь затем записать в базу данных. Рассмотрим подробнее особенности выполнения таких операций, проанализируем влияние контекста и разберем наиболее производительные способы загрузки.
Выбор места выполнения зависит от режима работы информационной базы и используемых методов чтения:
&НаСервере все равно изолирован. Передача больших коллекций (например, промежуточных таблиц) между клиентом и сервером требует времени на сериализацию данных во временное хранилище или реквизиты формы.ПоместитьВоВременноеХранилище(), но серверные мощности обычно выше клиентских рабочих станций.ТаблицаЗначений недоступен в тонком клиенте. Если чтение выполняется на клиенте, данные приходится считывать в массивы или структуры, либо использовать универсальные коллекции формы (ДанныеФормыКоллекция), что увеличивает расход памяти.Самый простой и кроссплатформенный вариант чтения файлов форматов .xls и .xlsx — использование встроенного объекта ТабличныйДокумент. Он работает как на клиенте, так и на сервере без необходимости установки сторонних программных пакетов.
Разберем по шагам, как прочитать файл и преобразовать его строки в коллекцию:
&НаСервере
Функция ПрочитатьФайлЧерезТабДок(АдресФайла)
ТабДок = Новый ТабличныйДокумент;
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ДвоичныеДанные.Записать(ИмяВременногоФайла);
ТабДок.Прочитать(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);
ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(50)));
ТаблицаРезультат.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ВсегоСтрок = ТабДок.ВысотаТаблицы;
Для НомерСтроки = 2 По ВсегоСтрок Цикл
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.Артикул = СокрЛП(ТабДок.ПолучитьОбласть(НомерСтроки, 1).Текст);
НоваяСтрока.Количество = Число(ТабДок.ПолучитьОбласть(НомерСтроки, 2).Текст);
КонецЦикла;
Возврат ТаблицаРезультат;
КонецФункции
При использовании COM-соединения частой ошибкой является поячеечный обход таблицы через Лист.Cells(Строка, Колонка).Value. Это приводит к тысячам межпроцессных вызовов и критически замедляет работу. Чтобы ускорить загрузку, следует считывать всю используемую область таблицы в двумерный COM-массив за одно обращение.
Посмотрим на пример выгрузки данных через свойство UsedRange:
&НаКлиенте
Процедура ЗагрузитьЧерезCOM(ПутьКФайлу)
Попытка
ExcelПриложение = Новый COMОбъект("Excel.Application");
ExcelПриложение.Visible = Ложь;
ExcelПриложение.DisplayAlerts = Ложь;
Книга = ExcelПриложение.Workbooks.Open(ПутьКФайлу);
Лист = Книга.Sheets(1);
// Считываем весь диапазон в память одним вызовом
МассивДанных = Лист.UsedRange.Value;
Книга.Close(Ложь);
ExcelПриложение.Quit();
Исключение
Если ExcelПриложение <> Неопределено Тогда
ExcelПриложение.Quit();
КонецЕсли;
ВызватьИсключение;
КонецПопытки;
// Обязательно очищаем ссылки для завершения процесса EXCEL.EXE
Лист = Неопределено;
Книга = Неопределено;
ExcelПриложение = Неопределено;
// Обработка SafeArray в 1С
КоличествоСтрок = МассивДанных.UBound(1);
Для НомерСтроки = 2 По КоличествоСтрок Цикл
ЗначениеКолонки1 = МассивДанных.GetValue(1, НомерСтроки);
ЗначениеКолонки2 = МассивДанных.GetValue(2, НомерСтроки);
КонецЦикла;
КонецПроцедуры
Для достижения максимальной скорости чтения больших файлов .xlsx на сервере без применения COM-объектов используют прямой потоковый разбор XML-структуры архива:
.xlsx распаковывается с помощью объекта ЧтениеZipФайла во временный каталог.ЧтениеXML считывается файл сопоставления строковых значений sharedStrings.xml в массив.sheet1.xml, где номера ячеек подставляются в результирующую структуру.Этот метод не блокирует память целиком под всю таблицу и работает в десятки раз быстрее, чем COM-соединение.
Если задача заключается в сравнении двух больших таблиц после их загрузки (например, исходной и подготовленной после транслитерации), избегайте вложенных циклов и линейного поиска. Проанализируем два эффективных подхода:
НайтиСтроки() обязательно добавьте индекс:
ТаблицаДанных.Индексы.Добавить("КодСопоставления");
Это переводит алгоритм поиска из полного перебора в логарифмический поиск по индексному дереву.МенеджерВременныхТаблиц и выполните сопоставление через оператор ПОЛНОЕ СОЕДИНЕНИЕ или ЛЕВОЕ СОЕДИНЕНИЕ средствами СУБД. Это минимизирует код и ускоряет вычисления.Для отображения хода длительного чтения файлов на клиенте используется процедура Состояние(). Однако, если тяжелая обработка выполняется на сервере, синхронный вызов заблокирует форму. В таком случае разбор и сравнение файлов выносят в фоновое задание через ФоновыеЗадания.Выполнить(), а форма опрашивает статус через ПодключитьОбработчикОжидания(), плавно обновляя визуальный индикатор без зависания интерфейса пользователя.