При разработке решений на платформе 1С:Предприятие часто возникает задача поиска данных в коллекциях, таких как массивы структур. Разберем, какие подходы существуют для поиска в массиве структур, какие из них наиболее эффективны, и как избежать распространенных ошибок, влияющих на корректность и производительность наших решений.
Когда мы работаем с массивом структур, одним из самых очевидных способов найти нужный элемент является прямой перебор всех элементов массива в цикле. Этот подход прост в реализации, и для небольших объемов данных он может быть вполне приемлем.
Рассмотрим пример такого перебора:
// Предположим, у нас есть массив структур
МассивСтруктур = Новый Массив();
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "001", "Товар 1"));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "002", "Товар 2"));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "003", "Товар 3"));
ИскомыйКод = "002";
НайденнаяСтруктура = Неопределено;
Для Каждого ТекСтруктура Из МассивСтруктур Цикл
Если ТекСтруктура.Код = ИскомыйКод Тогда
НайденнаяСтруктура = ТекСтруктура;
Прервать; // Нашли, выходим из цикла
КонецЕсли;
КонецЦикла;
Если НайденнаяСтруктура <> Неопределено Тогда
Сообщить("Нашли товар: " + НайденнаяСтруктура.Наименование);
Иначе
Сообщить("Товар с кодом " + ИскомыйКод + " не найден.");
КонецЕсли;
Однако, с увеличением объема данных в массиве, производительность такого перебора существенно снижается. Каждая операция поиска требует прохождения по части или всему массиву, что приводит к линейной зависимости времени выполнения от количества элементов. Это становится критичным, если такая операция поиска вызывается многократно, как в случае с нашим автором, который опасался снижения быстродействия при использовании других механизмов.
Одной из наиболее распространенных и коварных ошибок при работе с массивом структур, которая может привести к совершенно неожиданным результатам, является добавление одних и тех же объектов-структур по ссылке — поможет инструмент пошаговой отладки и просмотра переменных. Наш автор столкнулся с этой проблемой, когда выяснилось, что
«получился массив структур в котором все элементы равны последнему добавленному».
Выясним причину этой ситуации. В 1С объекты коллекций, такие как Структура, являются ссылочными типами. Это означает, что переменная хранит не саму структуру, а ссылку на нее в памяти. Если мы создаем одну структуру, а затем многократно добавляем ее в массив, изменяя перед каждым добавлением, то все элементы массива будут ссылаться на
один и тот же объект в памяти.
Когда мы изменяем этот объект, изменения отражаются во всех элементах массива.Рассмотрим некорректный пример:
МассивСтруктур = Новый Массив();
МояСтруктура = Новый Структура("Поле1, Поле2");
МояСтруктура.Поле1 = 1;
МояСтруктура.Поле2 = "Значение 1";
МассивСтруктур.Добавить(МояСтруктура); // Добавили ссылку на МояСтруктура
МояСтруктура.Поле1 = 2;
МояСтруктура.Поле2 = "Значение 2";
МассивСтруктур.Добавить(МояСтруктура); // Добавили ссылку на ту же МояСтруктура
МояСтруктура.Поле1 = 3;
МояСтруктура.Поле2 = "Значение 3";
МассивСтруктур.Добавить(МояСтруктура); // И снова ссылку на ту же МояСтруктура
// После этого цикла, все три элемента МассивСтруктур будут содержать
// значения: Поле1 = 3, Поле2 = "Значение 3"
// Так как все они ссылаются на один и тот же, последний измененный объект.
Для корректной работы, каждый раз при добавлении нового элемента, мы должны создавать
новый экземпляр структуры.
Это гарантирует, что каждый элемент массива будет уникальным объектом, а не ссылкой на один и тот же.Правильный способ добавления структур в массив:
МассивСтруктур = Новый Массив();
МассивСтруктур.Добавить(Новый Структура("Поле1, Поле2", 1, "Значение 1"));
МассивСтруктур.Добавить(Новый Структура("Поле1, Поле2", 2, "Значение 2"));
МассивСтруктур.Добавить(Новый Структура("Поле1, Поле2", 3, "Значение 3"));
// Теперь МассивСтруктур содержит три независимые структуры
// с соответствующими значениями.
Как видим, вместо того чтобы переиспользовать одну и ту же переменную МояСтруктура, мы каждый раз создаем новый объект Структура непосредственно в вызове метода Добавить(). Это критически важный момент для понимания корректного поведения коллекций в 1С и предотвращения многих ошибок.
После исправления ошибки с добавлением по ссылке, наш автор обнаружил, что метод Массив.Найти()
«прекрасно справлялся».
Однако, необходимо четко понимать, как работает этот метод применительно к структурам.Метод Массив.Найти(ИскомыйЭлемент) ищет в массиве элемент, который является
точно таким же объектом
(то есть, имеет ту же ссылку в памяти), что иИскомыйЭлемент. Он не сравнивает содержимое полей структур.
Если мы добавили структуры правильно (каждая как Новый Структура(...)), то каждая структура в массиве является уникальным объектом. В таком случае, Массив.Найти() будет работать только если мы передадим ему
точно ту же ссылку
на объект, который уже есть в массиве.Например, если мы хотим найти структуру с Поле1 = 2, мы не можем просто создать новую структуру Новый Структура("Поле1", 2) и ожидать, что Массив.Найти() ее обнаружит. Он не найдет, потому что это будет новый объект, отличный от тех, что уже есть в массиве, даже если их содержимое полей совпадает.
Работоспособность метода "Найти", о которой сообщил автор, скорее всего, связана с тем, что в его конкретном случае он либо искал ранее полученную ссылку на объект, либо механизм работы 1С в определенных условиях (например, при сравнении простых полей, если структура достаточно проста) мог приводить к неявным совпадениям объектов, что не является надежным поведением.
В общем случае, для поиска по содержимому полей структуры метод Массив.Найти() не предназначен.
Хотя массив структур может быть удобен для хранения упорядоченных наборов данных, для задач поиска и фильтрации в 1С часто существуют более производительные и подходящие объекты.
При выборе структуры данных, критически важно учитывать, как часто и по каким критериям будет осуществляться поиск. Если поиск осуществляется по одному или нескольким ключевым значениям, а объем данных значителен, использование
Соответствия
ТаблицыЗначений
Если нам необходимо быстро находить структуру по одному или нескольким ключевым полям (например, по коду товара, идентификатору пользователя), объект
Соответствие
Принцип работы:
ключ
из одного или нескольких полей нашей структуры, по которым будет осуществляться поиск.значения
мы сохраняем либо саму структуру, либо ссылку на нее, либо уникальный идентификатор, который позволит получить полную структуру из другого места.Рассмотрим пример, как преобразовать массив структур в Соответствие для быстрого поиска по полю Код:
// Исходный массив структур
МассивСтруктур = Новый Массив();
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "001", "Товар 1"));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "002", "Товар 2"));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование", "003", "Товар 3"));
// Создаем новое Соответствие для быстрого поиска
СоответствиеПоКоду = Новый Соответствие();
Для Каждого ТекСтруктура Из МассивСтруктур Цикл
// Ключом будет значение поля "Код", значением - сама структура
СоответствиеПоКоду.Вставить(ТекСтруктура.Код, ТекСтруктура);
КонецЦикла;
// Теперь поиск по коду очень быстрый:
ИскомыйКод = "002";
НайденнаяСтруктура = СоответствиеПоКоду.Получить(ИскомыйКод);
Если НайденнаяСтруктура <> Неопределено Тогда
Сообщить("Нашли товар через Соответствие: " + НайденнаяСтруктура.Наименование);
Иначе
Сообщить("Товар с кодом " + ИскомыйКод + " не найден в Соответствии.");
КонецЕсли;
// Если нам больше не нужен исходный массив, можем его очистить для экономии памяти
МассивСтруктур = Неопределено;
Такой подход обеспечивает практически мгновенный поиск по ключу, независимо от размера коллекции, что значительно превосходит производительность перебора массива. Для составных ключей можно использовать структуру или строку, формирующуюся из нескольких полей (например, Новый Структура("Поле1, Поле2", Значение1, Значение2) в качестве ключа).
Объект
ТаблицаЗначений
Автор в самом начале отказался от ТЗ из-за опасений на счет быстродействия, но это мнение не всегда оправдано.
ТаблицаЗначений
Методы поиска:
Предоставляет методыНайти(), НайтиСтроки(), которые эффективно ищут по одному или нескольким полям.Индексирование:
Мы можем создавать индексы по одной или нескольким колонкам, что значительно ускоряет операции поиска и сортировки, делая их сравнимыми по скорости с поиском вСоответствии.Методы фильтрации:
МетодыОтбор() и ВыбратьСтроки() позволяют фильтровать данные по сложным условиям.Передача по ссылке:
ПередачаТаблицыЗначений между клиентом и сервером по ссылке является эффективным способом работы с данными.Рассмотрим пример конвертации массива структур в ТаблицуЗначений и последующего поиска:
// Исходный массив структур
МассивСтруктур = Новый Массив();
МассивСтруктур.Добавить(Новый Структура("Код, Наименование, Цена", "001", "Товар 1", 100));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование, Цена", "002", "Товар 2", 200));
МассивСтруктур.Добавить(Новый Структура("Код, Наименование, Цена", "003", "Товар 3", 150));
// Создаем ТаблицуЗначений и определяем ее колонки
ТЗДанные = Новый ТаблицаЗначений();
ТЗДанные.Колонки.Добавить("Код");
ТЗДанные.Колонки.Добавить("Наименование");
ТЗДанные.Колонки.Добавить("Цена");
// Добавляем индекс по колонке "Код" для ускорения поиска
ТЗДанные.Индексы.Добавить("Код");
// Заполняем ТаблицуЗначений данными из массива структур
Для Каждого ТекСтруктура Из МассивСтруктур Цикл
НоваяСтрока = ТЗДанные.Добавить();
НоваяСтрока.Код = ТекСтруктура.Код;
НоваяСтрока.Наименование = ТекСтруктура.Наименование;
НоваяСтрока.Цена = ТекСтруктура.Цена;
КонецЦикла;
// Теперь выполняем поиск с помощью метода Найти() по индексированной колонке
ИскомыйКод = "002";
НайденнаяСтрока = ТЗДанные.Найти(ИскомыйКод, "Код");
Если НайденнаяСтрока <> Неопределено Тогда
Сообщить("Нашли товар через ТаблицуЗначений: " + НайденнаяСтрока.Наименование + ", Цена: " + НайденнаяСтрока.Цена);
Иначе
Сообщить("Товар с кодом " + ИскомыйКод + " не найден в ТаблицеЗначений.");
КонецЕсли;
// Поиск по нескольким полям с помощью НайтиСтроки()
ИскомоеНаименование = "Товар 3";
ИскомаяЦена = 150;
ОтборПоДвумПолям = Новый Структура("Наименование, Цена", ИскомоеНаименование, ИскомаяЦена);
НайденныеСтроки = ТЗДанные.НайтиСтроки(ОтборПоДвумПолям);
Если НайденныеСтроки.Количество() > 0 Тогда
Для Каждого Стр Из НайденныеСтроки Цикл
Сообщить("Нашли товар по наименованию и цене: " + Стр.Код + " - " + Стр.Наименование);
КонецЦикла;
Иначе
Сообщить("Товар с наименованием '" + ИскомоеНаименование + "' и ценой " + ИскомаяЦена + " не найден.");
КонецЕсли;
Для сложных сценариев, когда нужны гибкие запросы к данным, ТЗ с индексами обеспечивает значительное преимущество в производительности по сравнению с ручным перебором массива структур. Не стоит избегать ТаблицыЗначений из-за необоснованных опасений, лучше провести замеры производительности для своего конкретного случая.
Не всегда отказ от ТаблицыЗначений в пользу массива структур из-за страха снижения быстродействия оправдан. В большинстве случаев для поиска и фильтрации данных ТаблицаЗначений с индексами или методом НайтиСтроки является более производительным решением, особенно для больших объемов. Передача ТаблицыЗначений по ссылке между клиентом и сервером также эффективна.
Всегда проводите замеры!
Если для поиска требуется только одно или несколько полей, нет необходимости хранить в индексе или Соответствии всю структуру. Достаточно сохранять только ключевые поля и, возможно, ссылку на нужный объект. Это уменьшает объем памяти и ускоряет операции. Например, можно хранить только идентификатор записи вместо всей структуры, а саму структуру получать по идентификатору из другого места, если она потребуется.
В «Библиотеке стандартных подсистем» (БСП) часто содержатся готовые оптимизированные функции для работы с коллекциями. Например, функция ОбщегоНазначения.ВыгрузитьКолонку может помочь извлечь столбец значений из массива или таблицы для дальнейшего поиска. Однако стоит понимать, что эти функции также могут использовать перебор внутри себя, но их преимущество в том, что они уже написаны и протестированы.
Если есть сомнения в производительности какого-либо решения, особенно при работе с большими объемами данных или частых вызовах,
критически важно использовать инструменты профилирования
. Это может быть технологический журнал 1С, встроенный замер производительности или другие методики. Только замеры позволяют точно определить «узкие места» в коде и выбрать наиболее оптимальный подход для конкретной задачи.Таким образом, для эффективного поиска в массиве структур в 1С мы рекомендуем сначала убедиться в корректности добавления элементов (избегая ссылочной ошибки), а затем рассмотреть использование Соответствия для поиска по ключу или ТаблицыЗначений с индексами для более сложных сценариев. Прямой перебор остается вариантом для небольших коллекций, но не для высокопроизводительных задач.