Почему метод Найти() для массива файлов в 1С всегда возвращает Неопределено и как это исправить?

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

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда интуитивно понятный алгоритм отказывается работать. Типичный пример — сравнение файлов в разных каталогах. Мы пишем простой и логичный код, ожидая, что платформа сама сопоставит одинаковые элементы, но метод Найти() упорно возвращает Неопределено. Почему так происходит и как правильно решать подобные задачи, особенно когда речь идет о десятках тысяч файлов?

В этой статье мы подробно разберем внутренние механизмы работы платформы "1С:Предприятие 8" с объектами файловой системы, выясним фундаментальную причину такого поведения и рассмотрим несколько эффективных способов решения задачи синхронизации каталогов.

В чем суть проблемы? Анализируем исходный код

Рассмотрим типичный сценарий, с которого часто начинают разработчики. Нам нужно сравнить файлы в двух директориях (например, в рабочей папке и папке бэкапа) и найти совпадающие элементы. Мы пишем следующий код:


МассивФайлов1 = НайтиФайлы("c:\temp", "*.exe");
МассивФайлов2 = НайтиФайлы("c:\temp\1", "*.exe");

Для Каждого ЭлементМассив1 Из МассивФайлов1 Цикл
    Если МассивФайлов2.Найти(ЭлементМассив1) <> Неопределено Тогда 
         Сообщить("Элемент найден: " + ЭлементМассив1.Имя);
    КонецЕсли;
КонецЦикла;

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

Выясняем причину: как 1С сравнивает объекты в памяти

Чтобы понять, почему поиск не работает, нам нужно разграничить два понятия: простые типы данных (значения) и объектные типы.

Простые типы, такие как Строка, Число, Булево или Дата, сравниваются непосредственно по их значению. Строка "привет" всегда равна строке "привет", в каком бы месте оперативной памяти они ни находились.

С объектами ситуация иная. Объект типа Файл в 1С — это не сам файл на жестком диске. Это специальный объект-оболочка в оперативной памяти компьютера (ОЗУ), который содержит набор свойств (путь, размер, имя, дата изменения) и методов для работы с физическим файлом.

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

Проанализируем классический пример:


Файл1 = Новый Файл("C:\temp\data.txt");
Файл2 = Новый Файл("C:\temp\data.txt");

Если Файл1 = Файл2 Тогда
    // Этот код никогда не выполнится
    Сообщить("Файлы равны");
Иначе
    // Мы всегда попадем сюда
    Сообщить("Файлы НЕ равны");
КонецЕсли;

Несмотря на то, что оба объекта указывают на один и тот же физический файл на диске C:\temp\data.txt и обладают идентичными свойствами, в оперативной памяти это два абсолютно разных объекта. Они расположены по разным адресам ОЗУ. Именно поэтому сравнение Файл1 = Файл2 всегда возвращает Ложь, а метод массива Найти(), работающий по тому же принципу сравнения ссылок, никогда не находит совпадений.

Проблема квадратичной сложности алгоритма при больших объемах

Прежде чем перейти к правильным способам решения, важно обратить внимание на производительность. Допустим, нам удалось обойти ограничение сравнения ссылок. Если мы будем использовать обход одного массива в цикле и выполнять поиск во втором массиве через метод Найти(), мы получим алгоритм с так называемой квадратичной сложностью O(N * M).

Если в первом каталоге находится 10 000 файлов и во втором тоже 10 000 файлов, платформе придется выполнить до 100 000 000 (ста миллионов!) операций сравнения строк. Это гарантированно приведет к зависанию сеанса 1С на длительное время. Нам нужны более оптимальные структуры данных.

Рассмотрим по шагам три эффективных решения этой задачи.

Решение 1: Простой обход со сравнением имен файлов (для небольших массивов)

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

Выполним следующие действия по шагам:

  1. Получим массивы файлов из обоих каталогов.
  2. Создадим вспомогательный массив и заполним его только строковыми именами файлов из второго каталога.
  3. Обойдем первый массив файлов и проверим наличие имени каждого файла во вспомогательном массиве имен.

Реализуем этот алгоритм на практике:


МассивФайлов1 = НайтиФайлы("c:\temp", "*.exe");
МассивФайлов2 = НайтиФайлы("c:\temp\1", "*.exe");

// Шаг 1. Создаем массив только для имен файлов из второго каталога
МассивИмен2 = Новый Массив;
Для Каждого ТекущийФайл Из МассивФайлов2 Цикл
    МассивИмен2.Добавить(ТекущийФайл.Имя);
КонецЦикла;

// Шаг 2. Производим поиск по строковым именам
Для Каждого ЭлементМассив1 Из МассивФайлов1 Цикл
    ИндексНайденного = МассивИмен2.Найти(ЭлементМассив1.Имя);
    Если ИндексНайденного <> Неопределено Тогда
        Сообщить("Найден одинаковый файл по имени: " + ЭлементМассив1.Имя);
    КонецЕсли;
КонецЦикла;

Этот способ прост в написании, однако он все еще использует метод Найти() для массива, что при объемах более 5 000 файлов начнет заметно притормаживать систему. Перейдем к профессиональным инструментам оптимизации.

Решение 2: Использование объекта "Соответствие" для моментального поиска (Рекомендуется!)

Объект Соответствие (в других языках программирования известный как Map или ассоциативный массив) идеально подходит для решения задач поиска уникальных ключей. Поиск элемента в Соответствии по ключу происходит практически мгновенно благодаря механизму хеширования (сложность поиска близка к O(1)). Размер коллекции почти не влияет на скорость работы.

Проанализируем эффективный алгоритм с использованием Соответствия:


МассивФайлов1 = НайтиФайлы("c:\temp", "*.exe");
МассивФайлов2 = НайтиФайлы("c:\temp\1", "*.exe");

// Шаг 1. Переносим имена файлов второго каталога в Соответствие
// В качестве ключа используем имя файла (строку), значение может быть любым (например, Истина)
СоответствиеФайлов2 = Новый Соответствие;
Для Каждого ТекущийФайл Из МассивФайлов2 Цикл
    СоответствиеФайлов2.Вставить(ТекущийФайл.Имя, Истина);
КонецЦикла;

// Шаг 2. Быстро проверяем наличие файлов первого каталога во втором
Для Каждого ЭлементМассив1 Из МассивФайлов1 Цикл
    Если СоответствиеФайлов2[ЭлементМассив1.Имя] <> Неопределено Тогда
        Сообщить("Файл найден в обоих каталогах за миллисекунды: " + ЭлементМассив1.Имя);
    КонецЕсли;
КонецЦикла;

Преимущество метода: Данный код отработает для 50 000 файлов менее чем за секунду. Это стандарт де-факто для оптимизации поисковых запросов в коллекциях 1С.

Решение 3: Сравнение по множеству атрибутов с использованием индексированной Таблицы Значений

Иногда простого совпадения имен файлов недостаточно. Файлы могут называться одинаково, но иметь разное содержимое, разные размеры или даты модификации. Чтобы учесть эти параметры, мы можем применить объект ТаблицаЗначений с предварительно настроенными индексами.

Рассмотрим по шагам реализацию многокритериального поиска:


// Шаг 1. Создаем таблицу значений для описания файлов второго каталога
ТаблицаФайлов2 = Новый ТаблицаЗначений;
ТаблицаФайлов2.Колонки.Добавить("Имя", Новый ОписаниеТипов("Строка"));
ТаблицаФайлов2.Колонки.Добавить("Размер", Новый ОписаниеТипов("Число"));

// Шаг 2. Заполняем таблицу данными
МассивФайлов2 = НайтиФайлы("c:\temp\1", "*.exe");
Для Каждого Файл2 Из МассивФайлов2 Цикл
    НоваяСтрока = ТаблицаФайлов2.Добавить();
    НоваяСтрока.Имя = Файл2.Имя;
    НоваяСтрока.Размер = Файл2.Размер(); // Метод Размер() возвращает размер в байтах
КонецЦикла;

// Шаг 3. Строим индекс по ключевым полям для ускорения поиска
ТаблицаФайлов2.Индексы.Добавить("Имя, Размер");

// Шаг 4. Ищем совпадения из первого каталога
МассивФайлов1 = НайтиФайлы("c:\temp", "*.exe");
Для Каждого Файл1 Из МассивФайлов1 Цикл
    
    // Подготовим структуру отбора
    СтруктураПоиска = Новый Структура;
    СтруктураПоиска.Вставить("Имя", Файл1.Имя);
    СтруктураПоиска.Вставить("Размер", Файл1.Размер());
    
    // Поиск по индексированным колонкам выполняется мгновенно
    НайденныеСтроки = ТаблицаФайлов2.НайтиСтроки(СтруктураПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        Сообщить("Найден полностью идентичный файл: " + Файл1.Имя + " (" + Файл1.Размер() + " байт)");
    КонецЕсли;
    
КонецЦикла;

Альтернативный вариант: использование утилиты Robocopy

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

В операционной системе Windows есть встроенная мощная консольная утилита Robocopy, которая оптимизирована для зеркалирования директорий на низком уровне. Мы можем запустить ее прямо из кода 1С, передав необходимые параметры. Это отработает в десятки раз быстрее любого кода на встроенном языке.

Посмотрим на пример вызова внешней утилиты для синхронизации папок:


// Параметры запуска:
// /MIR - режим зеркалирования (копирует новые, удаляет отсутствующие в источнике файлы)
// /R:0 - количество повторных попыток при ошибке доступа (0 - не повторять)
// /W:0 - время ожидания между попытками
ПутьКИсточнику = "C:\temp";
ПутьКПриемнику = "C:\temp\1";
КомандаЗапуска = "robocopy """ + ПутьКИсточнику + """ """ + ПутьКПриемнику + """ *.exe /MIR /R:0 /W:0";

// Запускаем приложение в фоновом режиме, ожидая завершения работы
КодВозврата = Неопределено;
ЗапуститьПриложение(КомандаЗапуска, , Истина, КодВозврата);

Если КодВозврата <= 8 Тогда
    Сообщить("Синхронизация каталогов успешно завершена утилитой Robocopy!");
Иначе
    Сообщить("При синхронизации возникли ошибки. Код возврата: " + КодВозврата);
КонецЕсли;

Использование системных утилит снимает нагрузку с сервера 1С, избавляет от необходимости выделять гигабайты оперативной памяти под массивы объектов и решает задачу максимально надежно.

Резюме

Мы подробно разобрали, почему метод Найти() для массивов объектов типа Файл возвращает Неопределено. Главное — помнить, что объекты 1С сравниваются по ссылке в оперативной памяти, а не по значениям свойств на диске.

Для эффективной работы придерживайтесь следующих правил:

← На главную