При обработке масштабных реестров (например, базы недействительных паспортов или списков Росфинмониторинга) объемом от одного гигабайта разработчики 1С часто сталкиваются с системной ошибкой «Недостаточно памяти». Попытка открыть такой файл стандартными методами блокирует работу платформы или приводит к аварийному завершению сеанса. Проанализируем причины этой проблемы и разберем практические способы поиска нужных строк без падения системы.
Разберем внутренние механизмы платформы, которые приводят к возникновению ошибки:
ТекстовыйДокумент всегда пытается загрузить содержимое файла в оперативную память целиком. Для файлов размером в сотни мегабайт и гигабайты его использовать категорически нельзя.ЧтениеТекста сам по себе работает потоково. Однако если разработчик вызывает метод Прочитать() без указания количества символов вместо ПрочитатьСтроку(), файл снова загружается целиком. Вторая частая ошибка — сохранение каждой прочитанной строки в коллекцию (например, ТаблицаЗначений или Массив). Из-за фрагментации памяти при миллионах итераций процесс падает.Рассмотрим популярный и удобный подход: обращение к текстовому файлу как к реляционной таблице базы данных через технологию ADO. Это позволяет переложить фильтрацию строк на системный драйвер и получить в 1С только те записи, которые удовлетворяют условию поиска.
Для корректного распознавания полей драйвером рядом с исходным файлом создадим файл описания структуры Schema.ini, после чего выполним SQL-запрос с фильтром WHERE.
Посмотрим на пример функции, реализующей этот механизм:
Функция НайтиПаспортаВФайле(Папка, ИмяФайла, Серия, Номер)
// Создаем файл структуры Schema.ini для текстового драйвера
ТекстСхемы = "[" + ИмяФайла + "]" + Символы.ПС
+ "ColNameHeader=False" + Символы.ПС
+ "Format=Delimited(,)" + Символы.ПС
+ "CharacterSet=ANSI" + Символы.ПС
+ "Col1=Series Text" + Символы.ПС
+ "Col2=Number Text" + Символы.ПС;
ТекстДок = Новый ТекстовыйДокумент;
ТекстДок.УстановитьТекст(ТекстСхемы);
ТекстДок.Записать(Папка + "Schema.ini");
// Настраиваем подключение через ADODB
objConn = Новый COMОбъект("ADODB.Connection");
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Папка + ";Extended Properties=""text;""";
objConn.Open(strConn);
objRec = Новый COMОбъект("ADODB.Recordset");
// Накладываем условие сразу в SQL-запросе, чтобы не тащить лишние данные в 1С
strQuery = "SELECT * FROM [" + ИмяФайла + "] WHERE Series = '" + Серия + "' AND Number = '" + Номер + "'";
objRec.Open(strQuery, objConn, 3, 3, 1);
Найден = Ложь;
Если Не objRec.EOF Тогда
Найден = Истина;
КонецЕсли;
objRec.Close();
objConn.Close();
Возврат Найден;
КонецФункции
Важное ограничение: драйвер Microsoft.Jet.OLEDB.4.0 является 32-разрядным и имеет жесткий внутренний лимит на размер исходного файла в 2 Гб. Если распакованный реестр превысит этот предел, запрос завершится системной ошибкой драйвера.
Разберем наиболее надежный и кроссплатформенный алгоритм внутри чистой платформы 1С. Вместо того чтобы переносить весь гигантский файл в базу данных или искать каждую запись базы перебором файла, сменим вектор поиска:
Соответствие, где ключом будет строка вида «СерияНомер». Поиск в объекте Соответствие работает по хэш-таблице со сложностью O(1), то есть мгновенно.ЧтениеТекста и прочитаем его циклом строка за строкой с помощью метода ПрочитатьСтроку(). Память платформы при этом будет расходоваться исключительно на одну текущую строку.Рассмотрим реализацию данного метода:
Процедура ПроверитьБазуПоРеестру(ПолныйПутьКФайлу, ТаблицаПаспортовДляПроверки)
// Заполняем хэш-таблицу искомых значений в памяти
ИскомыеПаспорта = Новый Соответствие;
Для Каждого Стр Из ТаблицаПаспортовДляПроверки Цикл
КлючПоиска = СокрЛП(Стр.Серия) + СокрЛП(Стр.Номер);
ИскомыеПаспорта.Вставить(КлючПоиска, Стр.Ссылка);
КонецЦикла;
// Инициализируем потоковое чтение
ПотокЧтения = Новый ЧтениеТекста(ПолныйПутьКФайлу, КодировкаТекста.ANSI);
ТекущаяСтрока = ПотокЧтения.ПрочитатьСтроку();
НайденныеНедействительные = Новый Массив;
Пока ТекущаяСтрока <> Неопределено Цикл
// Нормализуем прочитанную строку под формат ключа
КлючИзФайла = СтрЗаменить(ТекущаяСтрока, ",", "");
КлючИзФайла = СтрЗаменить(КлючИзФайла, Символы.Таб, "");
КлючИзФайла = СокрЛП(КлючИзФайла);
СсылкаНаОбъект = ИскомыеПаспорта[КлючИзФайла];
Если СсылкаНаОбъект <> Неопределено Тогда
НайденныеНедействительные.Добавить(СсылкаНаОбъект);
КонецЕсли;
// Считываем следующую строку, старая удаляется сборщиком мусора
ТекущаяСтрока = ПотокЧтения.ПрочитатьСтроку();
КонецЦикла;
ПотокЧтения.Закрыть();
// Обрабатываем найденные совпадения
Для Каждого Найденный Из НайденныеНедействительные Цикл
Сообщить("Обнаружен недействительный паспорт у объекта: " + Строка(Найденный));
КонецЦикла;
КонецПроцедуры
Этот метод гарантирует стабильное потребление оперативной памяти (в пределах нескольких десятков мегабайт) независимо от того, сколько сотен миллионов строк находится в файле.
Если требуется разово проверить конкретную серию и номер паспорта прямо из файла без загрузки всего массива данных, эффективнее всего воспользоваться консольными утилитами операционной системы. Консольная утилита findstr в Windows или grep в Linux оптимизированы на уровне ядра ОС и считывают файл на максимальной скорости чтения дискового накопителя.
Посмотрим на вызов системной утилиты из кода 1С через объект ЗапуститьПриложение():
Функция НайтиСтрокуКонсольнойУтилитой(ПутьКФайлу, СтрокаПоиска, ФайлРезультата)
// Формируем команду для штатной утилиты findstr (Windows)
Команда = "cmd.exe /c findstr /B """ + СтрокаПоиска + """ """ + ПутьКФайлу + """ > """ + ФайлРезультата + """";
// Запускаем процесс синхронно с ожиданием завершения
КодВозврата = ЗапуститьПриложение(Команда, , Истина);
Найден = Ложь;
ФайлОтвета = Новый Файл(ФайлРезультата);
Если ФайлОтвета.Существует() И ФайлОтвета.Размер() > 0 Тогда
Найден = Истина;
КонецЕсли;
Возврат Найден;
КонецФункции
Когда 1С работает в файловом варианте и развертывание полноценного экземпляра MS SQL Server или PostgreSQL нецелесообразно, оптимальным решением для регулярных проверок является встраиваемая база данных SQLite. Файл с недействительными паспортами один раз загружается в компактный файл базы .db, где по полю номера паспорта строится B-tree индекс. Обращение к такой базе через COM-объект или внешнюю библиотеку выполняется за доли миллисекунды, не нагружая оперативную память сервера 1С.