Как ускорить преобразование длинных строк в UTF-8 в 1С 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При работе с платформой 1С 7.7 возникает частая и ресурсоемкая задача — преобразование очень длинных строк из внутренней кодировки (обычно Windows-1251) в UTF-8, особенно при необходимости выгрузки данных на внешние ресурсы, такие как веб-сайты. Когда длина строки достигает нескольких миллионов символов, стандартные методы встроенного языка 1С могут приводить к неприемлемому времени выполнения, измеряемому десятками минут. Разберем, почему это происходит и какие существуют эффективные решения для ускорения этого процесса.

Выясняем причину медленной работы исходного кода

Начнем с анализа типичного алгоритма, который часто используют разработчики, сталкиваясь с задачей преобразования кодировки: посимвольная обработка и конкатенация строк в цикле. Давайте посмотрим на предложенный пример:


Длина = СтрДлина(Стр);
Итог = "";
Для Н = 1 По Длина Цикл
    Знак = Сред(Стр, Н, 1);
    Код = КодСимв(Знак);
    Если Код < 128 Тогда
        Итог = Итог + Знак;
    Иначе
        Если (Код >= КодСимв("А")) И (Код <= КодСимв("п")) Тогда
            Итог = Итог + Симв(208) + Симв(144 + Код - КодСимв("А"));
        ИначеЕсли (Код >= КодСимв("р")) И (Код <= КодСимв("я")) Тогда
            Итог = Итог + Симв(209) + Симв(128 + Код - КодСимв("р"));
        ИначеЕсли (Знак = "ё") Тогда
            Итог = Итог + Симв(209) + Симв(145);
        ИначеЕсли (Знак = "Ё") Тогда
            Итог = Итог + Симв(208) + Симв(129);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Этот код, хотя и логичен с точки зрения перекодировки каждого символа, содержит несколько критических "узких мест", которые приводят к катастрофической потере производительности для длинных строк:

  1. Многократная конкатенация строк в цикле: Операция Итог = Итог + ... является основным виновником медленной работы. В 1С 7.7, при каждой такой операции, система вынуждена выделить новую область памяти, достаточную для хранения всей результирующей строки, скопировать в нее содержимое старой строки Итог, добавить новую часть, а затем освободить старую область памяти. Для строки длиной 6.5 миллионов символов, которая потенциально может увеличиться в размере при преобразовании в UTF-8 (например, один символ Windows-1251 может стать двумя байтами UTF-8), это означает миллионы операций выделения/освобождения памяти и копирования данных. Эти накладные расходы несоизмеримо велики.
  2. Посимвольная обработка: Встроенный язык 1С 7.7 является интерпретируемым, и выполнение цикла с миллионами итераций, где на каждой итерации происходят низкоуровневые операции (извлечение символа, определение его кода, сравнения, конкатенация), является крайне неэффективным.
  3. Многократный вызов функций: Функции КодСимв() и Симв() вызываются на каждой итерации цикла. Хотя сами по себе они не являются чрезвычайно медленными, их многократный вызов в таком объемном цикле также добавляет избыточные накладные расходы.

Таким образом, для эффективной работы с большими строками нам необходимо избегать посимвольной обработки и, самое главное, многократной конкатенации в цикле.

Решения для ускорения преобразования UTF-8

Рассмотрим несколько подходов, которые позволяют значительно ускорить процесс перекодировки.

1. Оптимизация нативного кода 1С (ограниченно)

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

  1. Предварительное вычисление констант: Мы можем вынести вызовы КодСимв() и Симв() для константных значений за пределы цикла, чтобы избежать их повторного вычисления.
    
    КодА = КодСимв("А");
    КодП = КодСимв("п");
    Кодр = КодСимв("р");
    Кодя = КодСимв("я");
    Симв208 = Симв(208);
    Симв209 = Симв(209);
    Симв145 = Симв(145); // для 'ё'
    Симв129 = Симв(129); // для 'Ё'
    
    // ... далее в цикле использовать эти переменные вместо КодСимв("А") и т.д.
    // Например: Итог = Итог + Симв208 + Симв(144 + Код - КодА);
    

    Эта оптимизация, однако, не решает фундаментальную проблему конкатенации.

  2. Использование СтрЗаменить() с таблицей замен: Вместо посимвольной обработки мы можем попытаться выполнить массовые замены. Для каждого кириллического символа, который преобразуется в UTF-8 по определенному правилу, мы можем заранее подготовить таблицу замен.
    
    ТЗЗамены = СоздатьОбъект("ТаблицаЗначений");
    ТЗЗамены.НоваяКолонка("Символ", "Строка", 1);
    ТЗЗамены.НоваяКолонка("ЗаменитьНа", "Строка");
    
    // Заполняем таблицу замен для всех кириллических символов
    // Пример для некоторых символов:
    ТЗЗамены.НоваяСтрока(); ТЗЗамены.Символ = "А"; ТЗЗамены.ЗаменитьНа = Симв(208) + Симв(144 + КодСимв("А") - КодСимв("А")); // D0 90
    ТЗЗамены.НоваяСтрока(); ТЗЗамены.Символ = "Б"; ТЗЗамены.ЗаменитьНа = Симв(208) + Симв(144 + КодСимв("Б") - КодСимв("А")); // D0 91
    // ... для всех символов от 'А' до 'Я' (D0 90 - D0 AF)
    // ... и от 'а' до 'п' (D0 B0 - D0 BF)
    // ... и от 'р' до 'я' (D1 80 - D1 8F)
    // ... и отдельных 'ё', 'Ё'
    ТЗЗамены.НоваяСтрока(); ТЗЗамены.Символ = "ё"; ТЗЗамены.ЗаменитьНа = Симв(209) + Симв(145); // D1 91
    ТЗЗамены.НоваяСтрока(); ТЗЗамены.Символ = "Ё"; ТЗЗамены.ЗаменитьНа = Симв(208) + Симв(129); // D0 81
    
    // Выполняем замены
    ТЗЗамены.ВыбратьСтроки();
    Пока ТЗЗамены.ПолучитьСтроку() = 1 Цикл
        Стр = СтрЗаменить(Стр, ТЗЗамены.Символ, ТЗЗамены.ЗаменитьНа);
    КонецЦикла;
    

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

    Мы советуем рассматривать такой метод только для строк умеренной длины или как улучшение, если другие методы недоступны.

2. Использование COM-объекта ADODB.Stream для быстрой перекодировки

Наиболее эффективным и рекомендованным способом для работы с кодировками больших объемов данных в 1С 7.7 является использование COM-объекта ADODB.Stream. Этот объект предоставляет мощные возможности для работы с потоками данных (файлами или памятью) и, самое главное, позволяет системными средствами операционной системы преобразовывать кодировки, что несравнимо быстрее встроенного языка 1С.

Платформа 1С 7.7 изначально работает с однобайтовыми строками в кодировке Windows-1251. При передаче таких строк в COM-объекты или при получении их из COM-объектов, 1С автоматически выполняет преобразование в двухбайтовое представление (Unicode) и обратно. Для очень длинных строк это преобразование само по себе может стать узким местом. Рассмотрим два основных подхода с использованием ADODB.Stream.

  1. Преобразование через временный файл:

    Этот метод является одним из самых надежных и производительных. Мы записываем исходную строку в один временный файл в кодировке Windows-1251, а затем используем ADODB.Stream для чтения этого файла с указанием исходной кодировки и записи в новый файл с целевой кодировкой UTF-8.

    
    // Предполагаем, что исходная строка для преобразования записана в файл
    // Например: СоздатьОбъект("Текст")->Записать(Путь + Имя, 0)->ЗаписатьСтроку(ИсходнаяСтрока);
    
    // Задаем параметры для преобразования
    Байт = 255; // Размер чанка для чтения (можно регулировать)
    Путь = "C:\Temp\"; // Путь к временным файлам
    ИмяИсходного = "ИсходныеДанные.txt";
    ИмяКонечного = "РезультатUTF8.txt";
    
    // Создаем объекты ADODB.Stream
    СтримВход = СоздатьОбъект("ADODB.Stream");
    СтримВход.Type = 2; // adTypeText - для работы с текстом
    СтримВход.charset = "windows-1251"; // Указываем исходную кодировку
    СтримВход.Open();
    СтримВход.LoadFromFile(Путь + ИмяИсходного); // Загружаем данные из исходного файла
    
    СтримВыход = СоздатьОбъект("ADODB.Stream");
    СтримВыход.Type = 2; // adTypeText
    СтримВыход.charset = "utf-8"; // Указываем целевую кодировку
    СтримВыход.LineSeparator = -1; // adCRLF - Unix-style line endings (или другие значения при необходимости)
    СтримВыход.Open();
    
    ВсегоБайт = СтримВход.size; // Общий размер файла для индикации прогресса
    
    // Читаем исходный файл частями и записываем в выходной поток
    Пока СтримВход.EOS = 0 Цикл // Пока не достигнут конец входного потока
        // Читаем порцию текста из входного потока (в указанной кодировке)
        // Символ '?' используется как тернарный оператор 1С для условной записи
        // Если Байт = -2, то ReadText читает весь оставшийся текст.
        // Если Байт -2, то ReadText читает указанное количество символов.
        // При WriteText второй параметр (true/false) указывает, добавлять ли символ переноса строки.
        СтримВыход.WriteText(СтримВход.ReadText(Байт), ?(Байт = -2, 1, 0)); 
        Состояние("Прогресс: " + Формат(100 * СтримВход.position / ВсегоБайт, "Ч(0)5.2") + "%");
    КонецЦикла;
    
    // Сохраняем результат в файл
    СтримВыход.SaveToFile(Путь + ИмяКонечного, 2); // 2 = adSaveCreateOverWrite - перезаписать, если файл существует
    
    СтримВход.Close();
    СтримВыход.Close();
    

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

  2. Преобразование в памяти с использованием MSScriptControl.ScriptControl:

    ADODB.Stream может работать и полностью в памяти, что позволяет избежать дисковых операций. Однако прямая передача очень длинной строки из 1С 7.7 в метод COM-объекта может быть проблематичной из-за внутренних преобразований кодировок и ограничений на размер строки, передаваемой COM-объекту. В таких случаях мы используем MSScriptControl.ScriptControl как "мостик" для выполнения VBScript или JScript кода, который уже непосредственно работает с ADODB.Stream.

    Мы рассмотрим пример VBScript функции, которая выполняет перекодировку, а затем обсудим, как её использовать из 1С 7.7 и как обойти проблемы с длинными строками.

    
    // Функция VBScript для перекодировки строки
    // Добавляется в MSScriptControl.ScriptControl
    /*
    Function StrConv(Text, SourceCharset, DestCharset)
        Set Stream = CreateObject("ADODB.Stream")
        Stream.Type = 2 ' adTypeText
        Stream.Mode = 3 ' adModeReadWrite
        Stream.Open
        Stream.Charset = DestCharset
        Stream.WriteText Text
        Stream.Position = 0
        Stream.Charset = SourceCharset
        StrConv = Stream.ReadText
    End Function
    */
    
    // Пример использования в 1С 7.7
    Процедура ПерекодироватьСтрокуВПамяти(ИсходнаяСтрока, НужнаяКодировка) Экспорт
        ScrptCtrl = СоздатьОбъект("MSScriptControl.ScriptControl");
        ScrptCtrl.Language = "VBScript";
        ScrptCtrl.AddCode("
            |Function StrConv_Internal(Text, SourceCharset, DestCharset)
            |    Set Stream = CreateObject(""ADODB.Stream"")
            |    Stream.Type = 2
            |    Stream.Mode = 3
            |    Stream.Open
            |    Stream.Charset = DestCharset
            |    Stream.WriteText Text
            |    Stream.Position = 0
            |    Stream.Charset = SourceCharset
            |    StrConv_Internal = Stream.ReadText
            |    Stream.Close
            |    Set Stream = Nothing
            |End Function
            |");
        
        // В 1С 7.7 строки по умолчанию считаются Windows-1251 при передаче в COM-объекты
        ИсходнаяКодировка1С = "windows-1251"; 
        
        // Вызываем VBScript функцию для перекодировки
        // Важно: для очень длинных строк (более ~2-4 МБ) прямая передача может вызвать ошибки.
        // В этом случае потребуется разбить ИсходнаяСтрока на части (чанками)
        // и конвертировать каждую часть отдельно, а затем собрать результат.
        РезультатUTF8 = ScrptCtrl.Run("StrConv_Internal", ИсходнаяСтрока, ИсходнаяКодировка1С, НужнаяКодировка);
        
        // Далее работаем с РезультатUTF8
        Сообщить("Перекодированная строка получена. Длина: " + СтрДлина(РезультатUTF8));
    КонецПроцедуры
    

    Важное замечание о длинных строках: Как уже упоминалось, прямая передача строки длиной 6.5 миллионов символов из переменной 1С 7.7 в COM-объект (даже через MSScriptControl.ScriptControl) может привести к проблемам: либо к "падению" 1С, либо к очень долгой работе из-за внутренних преобразований 1С-строк в Unicode для COM-объектов. Если вы сталкиваетесь с такими проблемами, необходимо разбить исходную строку на более мелкие части (чанки) и передавать их в VBScript функцию по очереди, а затем собирать результат.

    Например, мы можем разбить строку на части по 100 000 - 500 000 символов, конвертировать каждую часть, а затем собрать их в одну результирующую строку. При этом сборку также желательно делать через ADODB.Stream, чтобы избежать медленной конкатенации в 1С:

    
    // Пример фрагмента для сборки результата из частей
    СтримДляСборки = СоздатьОбъект("ADODB.Stream");
    СтримДляСборки.Type = 2; // adTypeText
    СтримДляСборки.charset = "utf-8"; // Кодировка результирующей строки
    СтримДляСборки.Open();
    
    // ... в цикле по чанкам ИсходнаяСтрока:
    //    ЧастьСтроки = Сред(ИсходнаяСтрока, Начало, ДлинаЧасти);
    //    РезультатЧастиUTF8 = ScrptCtrl.Run("StrConv_Internal", ЧастьСтроки, ИсходнаяКодировка1С, НужнаяКодировка);
    //    СтримДляСборки.WriteText(РезультатЧастиUTF8);
    // ...
    
    // После обработки всех частей
    СтримДляСборки.Position = 0;
    ИтоговаяСтрокаUTF8 = СтримДляСборки.ReadText();
    СтримДляСборки.Close();
    

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

3. Внешние компоненты или утилиты

Для максимально возможной производительности, особенно если перекодировка требуется очень часто или для крайне больших объемов данных, мы можем рассмотреть использование внешних решений:

  1. Внешние компоненты (DLL) на C++:

    Разработка собственной внешней компоненты (DLL) на низкоуровневом языке, таком как C++, позволяет выполнять перекодировку на машинном уровне. Такие компоненты могут быть оптимизированы для эффективного использования памяти и работы со строками, что обеспечивает наивысшую скорость. Интеграция с 1С 7.7 осуществляется через технологию COM. Это решение требует навыков программирования на C++ и понимания COM-взаимодействия.

  2. Отдельные EXE-утилиты:

    Мы также можем создать или использовать готовую утилиту (EXE-файл) для перекодировки. Принцип работы такой: 1С 7.7 записывает данные в исходном формате (Windows-1251) во временный файл, затем запускает внешнюю утилиту, которая считывает этот файл, перекодирует его в UTF-8 и записывает результат в другой файл. После этого 1С считывает уже перекодированный файл. Примером такой утилиты может служить даже Notepad++ (хотя его запуск для автоматизированной работы менее удобен) или специализированные консольные конвертеры.

  3. Компонента Formex:

    В некоторых случаях, возможно, вам пригодится сторонняя компонента Formex, которая, по сообщениям, имеет встроенные функции для кодирования в UTF-8 (хотя упоминается, что только в одну сторону). Если Formex уже используется в вашей конфигурации, стоит изучить её возможности по перекодировке строк.

Общие рекомендации при работе с большими строками в 1С 7.7

Подводя итог, мы хотим дать несколько общих советов по работе с большими строками в 1С 7.7:

  1. Избегайте конкатенации в циклах: Это самое главное правило. Если вам нужно собрать большую строку, используйте промежуточные файлы, ADODB.Stream или внешние механизмы, которые позволяют избежать множественных перевыделений памяти.
  2. Осознавайте внутренние механизмы 1С 7.7: Помните, что 1С 7.7 использует однобайтовую кодировку Windows-1251 для своих строк. При взаимодействии с COM-объектами происходит неявное преобразование в двухбайтовый Unicode, что для очень больших строк может быть затратным по времени и памяти.
  3. При работе с JSON: Если вы формируете JSON, постарайтесь максимально использовать возможности внешних объектов для его сборки и кодирования. Многие библиотеки для работы с JSON имеют функции для непосредственной записи в поток (файл или память), что значительно эффективнее, чем сборка всей JSON-строки в переменной 1С и её последующая перекодировка.
  4. Профилирование: Всегда профилируйте ваш код. Если какой-то участок работает медленно, используйте функцию Сообщить() с замером времени для определения точного "узкого места".

Применяя эти подходы, мы сможем добиться значительного ускорения процесса преобразования длинных строк в UTF-8 в 1С 7.7, превратив задачу, занимающую десятки минут, в операцию, выполняющуюся за секунды.

← На главную