Как выполнить поиск данных в очень большом текстовом файле в 1С без ошибки нехватки памяти

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

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

Выясним причину: почему платформа 1С исчерпывает оперативную память

Разберем внутренние механизмы платформы, которые приводят к возникновению ошибки:

  1. Ограничения 32-разрядной архитектуры. Если клиентское приложение или рабочий процесс сервера работает в 32-битном режиме, адресному пространству процесса доступно не более 2–3 Гб оперативной памяти. Текстовые данные при загрузке в память преобразуются платформой в кодировку UTF-16, из-за чего реальный объем файла на диске в 1,2 Гб в оперативной памяти разрастается до 2,5–3 Гб, моментально исчерпывая лимит.
  2. Неправильный выбор объекта платформы. Объект ТекстовыйДокумент всегда пытается загрузить содержимое файла в оперативную память целиком. Для файлов размером в сотни мегабайт и гигабайты его использовать категорически нельзя.
  3. Ошибки при использовании потокового чтения. Объект ЧтениеТекста сам по себе работает потоково. Однако если разработчик вызывает метод Прочитать() без указания количества символов вместо ПрочитатьСтроку(), файл снова загружается целиком. Вторая частая ошибка — сохранение каждой прочитанной строки в коллекцию (например, ТаблицаЗначений или Массив). Из-за фрагментации памяти при миллионах итераций процесс падает.

Способ 1. Выборка через драйвер Microsoft OLEDB (ADO)

Рассмотрим популярный и удобный подход: обращение к текстовому файлу как к реляционной таблице базы данных через технологию 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 Гб. Если распакованный реестр превысит этот предел, запрос завершится системной ошибкой драйвера.

Способ 2. Правильное потоковое чтение через «Соответствие» (поиск от меньшего к большему)

Разберем наиболее надежный и кроссплатформенный алгоритм внутри чистой платформы 1С. Вместо того чтобы переносить весь гигантский файл в базу данных или искать каждую запись базы перебором файла, сменим вектор поиска:

  1. Выгрузим из базы 1С все паспорта, которые требуется проверить (например, несколько тысяч записей контрагентов или сотрудников).
  2. Поместим их в объект Соответствие, где ключом будет строка вида «СерияНомер». Поиск в объекте Соответствие работает по хэш-таблице со сложностью O(1), то есть мгновенно.
  3. Откроем огромный файл через ЧтениеТекста и прочитаем его циклом строка за строкой с помощью метода ПрочитатьСтроку(). Память платформы при этом будет расходоваться исключительно на одну текущую строку.

Рассмотрим реализацию данного метода:


Процедура ПроверитьБазуПоРеестру(ПолныйПутьКФайлу, ТаблицаПаспортовДляПроверки)
    // Заполняем хэш-таблицу искомых значений в памяти
    ИскомыеПаспорта = Новый Соответствие;
    Для Каждого Стр Из ТаблицаПаспортовДляПроверки Цикл
        КлючПоиска = СокрЛП(Стр.Серия) + СокрЛП(Стр.Номер);
        ИскомыеПаспорта.Вставить(КлючПоиска, Стр.Ссылка);
    КонецЦикла;
    
    // Инициализируем потоковое чтение
    ПотокЧтения = Новый ЧтениеТекста(ПолныйПутьКФайлу, КодировкаТекста.ANSI);
    ТекущаяСтрока = ПотокЧтения.ПрочитатьСтроку();
    
    НайденныеНедействительные = Новый Массив;
    
    Пока ТекущаяСтрока <> Неопределено Цикл
        // Нормализуем прочитанную строку под формат ключа
        КлючИзФайла = СтрЗаменить(ТекущаяСтрока, ",", "");
        КлючИзФайла = СтрЗаменить(КлючИзФайла, Символы.Таб, "");
        КлючИзФайла = СокрЛП(КлючИзФайла);
        
        СсылкаНаОбъект = ИскомыеПаспорта[КлючИзФайла];
        Если СсылкаНаОбъект <> Неопределено Тогда
            НайденныеНедействительные.Добавить(СсылкаНаОбъект);
        КонецЕсли;
        
        // Считываем следующую строку, старая удаляется сборщиком мусора
        ТекущаяСтрока = ПотокЧтения.ПрочитатьСтроку();
    КонецЦикла;
    
    ПотокЧтения.Закрыть();
    
    // Обрабатываем найденные совпадения
    Для Каждого Найденный Из НайденныеНедействительные Цикл
        Сообщить("Обнаружен недействительный паспорт у объекта: " + Строка(Найденный));
    КонецЦикла;
КонецПроцедуры

Этот метод гарантирует стабильное потребление оперативной памяти (в пределах нескольких десятков мегабайт) независимо от того, сколько сотен миллионов строк находится в файле.

Способ 3. Использование системных утилит операционной системы

Если требуется разово проверить конкретную серию и номер паспорта прямо из файла без загрузки всего массива данных, эффективнее всего воспользоваться консольными утилитами операционной системы. Консольная утилита findstr в Windows или grep в Linux оптимизированы на уровне ядра ОС и считывают файл на максимальной скорости чтения дискового накопителя.

Посмотрим на вызов системной утилиты из кода 1С через объект ЗапуститьПриложение():


Функция НайтиСтрокуКонсольнойУтилитой(ПутьКФайлу, СтрокаПоиска, ФайлРезультата)
    // Формируем команду для штатной утилиты findstr (Windows)
    Команда = "cmd.exe /c findstr /B """ + СтрокаПоиска + """ """ + ПутьКФайлу + """ > """ + ФайлРезультата + """";
    
    // Запускаем процесс синхронно с ожиданием завершения
    КодВозврата = ЗапуститьПриложение(Команда, , Истина);
    
    Найден = Ложь;
    ФайлОтвета = Новый Файл(ФайлРезультата);
    Если ФайлОтвета.Существует() И ФайлОтвета.Размер() > 0 Тогда
        Найден = Истина;
    КонецЕсли;
    
    Возврат Найден;
КонецФункции

Способ 4. Локальная СУБД SQLite для файловых баз

Когда 1С работает в файловом варианте и развертывание полноценного экземпляра MS SQL Server или PostgreSQL нецелесообразно, оптимальным решением для регулярных проверок является встраиваемая база данных SQLite. Файл с недействительными паспортами один раз загружается в компактный файл базы .db, где по полю номера паспорта строится B-tree индекс. Обращение к такой базе через COM-объект или внешнюю библиотеку выполняется за доли миллисекунды, не нагружая оперативную память сервера 1С.

← На главную