При работе с платформой 1С 7.7 возникает частая и ресурсоемкая задача — преобразование очень длинных строк из внутренней кодировки (обычно Windows-1251) в UTF-8, особенно при необходимости выгрузки данных на внешние ресурсы, такие как веб-сайты. Когда длина строки достигает нескольких миллионов символов, стандартные методы встроенного языка 1С могут приводить к неприемлемому времени выполнения, измеряемому десятками минут. Разберем, почему это происходит и какие существуют эффективные решения для ускорения этого процесса.
Начнем с анализа типичного алгоритма, который часто используют разработчики, сталкиваясь с задачей преобразования кодировки: посимвольная обработка и конкатенация строк в цикле. Давайте посмотрим на предложенный пример:
Длина = СтрДлина(Стр);
Итог = "";
Для Н = 1 По Длина Цикл
Знак = Сред(Стр, Н, 1);
Код = КодСимв(Знак);
Если Код < 128 Тогда
Итог = Итог + Знак;
Иначе
Если (Код >= КодСимв("А")) И (Код <= КодСимв("п")) Тогда
Итог = Итог + Симв(208) + Симв(144 + Код - КодСимв("А"));
ИначеЕсли (Код >= КодСимв("р")) И (Код <= КодСимв("я")) Тогда
Итог = Итог + Симв(209) + Симв(128 + Код - КодСимв("р"));
ИначеЕсли (Знак = "ё") Тогда
Итог = Итог + Симв(209) + Симв(145);
ИначеЕсли (Знак = "Ё") Тогда
Итог = Итог + Симв(208) + Симв(129);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Этот код, хотя и логичен с точки зрения перекодировки каждого символа, содержит несколько критических "узких мест", которые приводят к катастрофической потере производительности для длинных строк:
Итог = Итог + ... является основным виновником медленной работы. В 1С 7.7, при каждой такой операции, система вынуждена выделить новую область памяти, достаточную для хранения всей результирующей строки, скопировать в нее содержимое старой строки Итог, добавить новую часть, а затем освободить старую область памяти. Для строки длиной 6.5 миллионов символов, которая потенциально может увеличиться в размере при преобразовании в UTF-8 (например, один символ Windows-1251 может стать двумя байтами UTF-8), это означает миллионы операций выделения/освобождения памяти и копирования данных. Эти накладные расходы несоизмеримо велики.КодСимв() и Симв() вызываются на каждой итерации цикла. Хотя сами по себе они не являются чрезвычайно медленными, их многократный вызов в таком объемном цикле также добавляет избыточные накладные расходы.Таким образом, для эффективной работы с большими строками нам необходимо избегать посимвольной обработки и, самое главное, многократной конкатенации в цикле.
Рассмотрим несколько подходов, которые позволяют значительно ускорить процесс перекодировки.
Хотя мы выяснили, что посимвольная конкатенация крайне неэффективна, существуют способы немного улучшить производительность, если все же необходимо остаться в рамках встроенного языка 1С. Эти методы могут помочь для строк среднего размера или при отсутствии других возможностей.
КодСимв() и Симв() для константных значений за пределы цикла, чтобы избежать их повторного вычисления.
КодА = КодСимв("А");
КодП = КодСимв("п");
Кодр = КодСимв("р");
Кодя = КодСимв("я");
Симв208 = Симв(208);
Симв209 = Симв(209);
Симв145 = Симв(145); // для 'ё'
Симв129 = Симв(129); // для 'Ё'
// ... далее в цикле использовать эти переменные вместо КодСимв("А") и т.д.
// Например: Итог = Итог + Симв208 + Симв(144 + Код - КодА);
Эта оптимизация, однако, не решает фундаментальную проблему конкатенации.
СтрЗаменить() с таблицей замен: Вместо посимвольной обработки мы можем попытаться выполнить массовые замены. Для каждого кириллического символа, который преобразуется в 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 или очень медленной работе.
Мы советуем рассматривать такой метод только для строк умеренной длины или как улучшение, если другие методы недоступны.
Наиболее эффективным и рекомендованным способом для работы с кодировками больших объемов данных в 1С 7.7 является использование COM-объекта ADODB.Stream. Этот объект предоставляет мощные возможности для работы с потоками данных (файлами или памятью) и, самое главное, позволяет системными средствами операционной системы преобразовывать кодировки, что несравнимо быстрее встроенного языка 1С.
Платформа 1С 7.7 изначально работает с однобайтовыми строками в кодировке Windows-1251. При передаче таких строк в COM-объекты или при получении их из COM-объектов, 1С автоматически выполняет преобразование в двухбайтовое представление (Unicode) и обратно. Для очень длинных строк это преобразование само по себе может стать узким местом. Рассмотрим два основных подхода с использованием ADODB.Stream.
Этот метод является одним из самых надежных и производительных. Мы записываем исходную строку в один временный файл в кодировке 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();
Этот код эффективно обрабатывает большие файлы, читая и записывая их по частям (чанками), что снижает нагрузку на память. Индикатор прогресса помогает отслеживать выполнение.
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С, что сделает процесс более быстрым и стабильным.
Для максимально возможной производительности, особенно если перекодировка требуется очень часто или для крайне больших объемов данных, мы можем рассмотреть использование внешних решений:
Разработка собственной внешней компоненты (DLL) на низкоуровневом языке, таком как C++, позволяет выполнять перекодировку на машинном уровне. Такие компоненты могут быть оптимизированы для эффективного использования памяти и работы со строками, что обеспечивает наивысшую скорость. Интеграция с 1С 7.7 осуществляется через технологию COM. Это решение требует навыков программирования на C++ и понимания COM-взаимодействия.
Мы также можем создать или использовать готовую утилиту (EXE-файл) для перекодировки. Принцип работы такой: 1С 7.7 записывает данные в исходном формате (Windows-1251) во временный файл, затем запускает внешнюю утилиту, которая считывает этот файл, перекодирует его в UTF-8 и записывает результат в другой файл. После этого 1С считывает уже перекодированный файл. Примером такой утилиты может служить даже Notepad++ (хотя его запуск для автоматизированной работы менее удобен) или специализированные консольные конвертеры.
В некоторых случаях, возможно, вам пригодится сторонняя компонента Formex, которая, по сообщениям, имеет встроенные функции для кодирования в UTF-8 (хотя упоминается, что только в одну сторону). Если Formex уже используется в вашей конфигурации, стоит изучить её возможности по перекодировке строк.
Подводя итог, мы хотим дать несколько общих советов по работе с большими строками в 1С 7.7:
ADODB.Stream или внешние механизмы, которые позволяют избежать множественных перевыделений памяти.Сообщить() с замером времени для определения точного "узкого места".Применяя эти подходы, мы сможем добиться значительного ускорения процесса преобразования длинных строк в UTF-8 в 1С 7.7, превратив задачу, занимающую десятки минут, в операцию, выполняющуюся за секунды.