Запись ДвоичныхДанных на диск в 1С 8.3: от одной строки кода до потоков

· 9 мин

Содержание

Файл из двоичных данных в 1С создаётся одной строкой: ДвоичныеДанные.Записать("C:\Выгрузка\счет.pdf"). Метод берёт байты из объекта и пишет их на диск как есть, без преобразований. Если файл с таким именем уже есть, он перезаписывается.

Сложности начинаются дальше. Файл появляется не на том компьютере, текст открывается «кракозябрами», выгрузка на 2 ГБ съедает память сервера. Для каждого из этих случаев есть свой приём, и все они работают на платформе 1С:Предприятие 8.3. Тема общая для платформы и не зависит от конфигурации: код одинаково работает в Бухгалтерии, ЗУП, УТ и самописных базах.

Откуда берутся двоичные данные, потоки и буфер

ДвоичныеДанные — неизменяемый объект: набор байтов целиком в памяти. Изменить байт внутри него нельзя, можно только получить новый объект. Для пошаговой работы с байтами в платформе 8.3.9 появились потоки и буфер.

ОбъектЧто этоКогда нужен
ДвоичныеДанныеготовый набор байтов в памятифайл до сотен мегабайт, простая запись
БуферДвоичныхДанныхизменяемый массив байтов фиксированного размераправка отдельных байтов, чтение кусками
ФайловыйПотокпоток, привязанный к файлу на дискезапись по частям, большие файлы
ПотокВПамятипоток в оперативной памятисборка файла из частей перед выгрузкой
ЗаписьДанных«писатель» поверх потоказапись строк, чисел, байтов в нужной кодировке

Где обычно лежат двоичные данные, которые нужно превратить в файл:

  • 📦 реквизит типа ХранилищеЗначения — ДД = Объект.Файл.Получить();
  • 🌐 ответ HTTP-сервиса — ДД = Ответ.ПолучитьТелоКакДвоичныеДанные();
  • 🔤 строка Base64 из XML или JSON — ДД = Base64Значение(Строка);
  • 📎 присоединённые файлы в типовых конфигурациях на БСП — через РаботаСФайлами.ДвоичныеДанныеФайла(Ссылка);
  • 🗂 временное хранилище после загрузки файла пользователем — ДД = ПолучитьИзВременногоХранилища(Адрес).

Перед записью проверьте тип: ТипЗнч(ДД) = Тип("ДвоичныеДанные"). Хранилище значения без .Получить() или пустое значение Неопределено — самая частая причина ошибки «Метод объекта не обнаружен (Записать)».

Как записать файл из двоичных данных на диск: пошагово

Способ 1. Метод Записать

Самый короткий путь. Подходит для 95% бытовых задач: сохранить вложение, выгрузить картинку, записать PDF.

ДД = Base64Значение(СтрокаBase64);
ИмяФайла = "C:\Выгрузка\счет_125.pdf";
ДД.Записать(ИмяФайла);

Порядок действий:

  1. Получите объект ДвоичныеДанные из источника.
  2. Соберите полное имя файла: каталог, имя, расширение.
  3. Убедитесь, что каталог существует. Если нет — создайте его через СоздатьКаталог(Путь).
  4. Вызовите ДД.Записать(ИмяФайла).
  5. Проверьте результат: Новый Файл(ИмяФайла).Существует().

Способ 2. Временный файл

Когда файл нужен ненадолго — открыть во внешней программе, отправить по почте, передать в обработку — пишите его во временный каталог:

ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
ДД.Записать(ИмяФайла);
// ... работа с файлом ...
УдалитьФайлы(ИмяФайла);

ПолучитьИмяВременногоФайла только придумывает уникальное имя в каталоге временных файлов. Сам файл не создаётся, и платформа его не удаляет. Удаление — ваша забота.

Способ 3. Через файловый поток

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

Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Попытка
    ДД.ОткрытьПотокДляЧтения().КопироватьВ(Поток);
    Поток.Закрыть();
Исключение
    Поток.Закрыть();
    ВызватьИсключение;
КонецПопытки;

РежимОткрытияФайла.Создать создаёт новый файл или перезаписывает существующий. Чтобы дописать данные в конец имеющегося файла, используйте РежимОткрытияФайла.Дописать.

Способ 4. Сборка в памяти, затем одна запись

Если файл складывается из нескольких кусков — заголовок, тело, подпись — и записывать полуготовый результат на диск нельзя, собирайте его в ПотокВПамяти:

Поток = Новый ПотокВПамяти;
Заголовок.ОткрытьПотокДляЧтения().КопироватьВ(Поток);
Тело.ОткрытьПотокДляЧтения().КопироватьВ(Поток);
ДД = Поток.ЗакрытьИПолучитьДвоичныеДанные();
ДД.Записать(ИмяФайла);

Плюс подхода: на диске никогда не окажется «половины» файла. Если сборка упала на втором куске, файла просто не будет, и принимающая программа не заберёт битые данные. Минус — весь результат живёт в памяти, поэтому для гигабайтных файлов этот способ не годится.

Где появится файл: сервер или компьютер пользователя

Это главный подвох. ДД.Записать() пишет туда, где выполняется код.

  • 🖥 Код в процедуре &НаСервере или в общем модуле с флагом «Сервер» — файл ляжет на диск сервера 1С. В клиент-серверной базе это другой компьютер, а не ПК бухгалтера.
  • 💻 Код &НаКлиенте в тонком клиенте — файл ляжет на компьютер пользователя.
  • 🌍 Веб-клиент к локальному диску напрямую не пишет: нужен асинхронный вызов и диалог сохранения в браузере.

Правильная схема выгрузки файла пользователю: данные готовятся на сервере, кладутся во временное хранилище, а клиент забирает их к себе.

&НаСервере
Функция ПодготовитьФайлНаСервере()
    ДД = Справочники.Договоры.НайтиПоКоду("000000125").Скан.Получить();
    Возврат ПоместитьВоВременноеХранилище(ДД, УникальныйИдентификатор);
КонецФункции

&НаКлиенте
Процедура СохранитьНаДиск(Команда)
    Адрес = ПодготовитьФайлНаСервере();
    ПолучаемыеФайлы = Новый Массив;
    ПолучаемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла("договор_125.pdf", Адрес));
    НачатьПолучениеФайлов(Новый ОписаниеОповещения("ПослеСохранения", ЭтотОбъект),
        ПолучаемыеФайлы, , Истина);
КонецПроцедуры

&НаКлиенте
Процедура ПослеСохранения(ПолученныеФайлы, ДополнительныеПараметры) Экспорт
    Если ПолученныеФайлы <> Неопределено Тогда
        ПоказатьОповещениеПользователя("Файл сохранён");
    КонецЕсли;
КонецПроцедуры

Последний параметр Истина открывает диалог выбора места сохранения. Так код работает и в тонком, и в веб-клиенте, без модальных окон.

В типовых конфигурациях на БСП то же самое делает одна процедура ФайловаяСистемаКлиент.СохранитьФайл. Если пишете расширение к типовой, используйте её: она учитывает веб-клиент и настройки пользователя.

Текст, кодировки и BOM при создании файла

Двоичные данные кодировок не знают — это просто байты. Кодировка появляется в момент, когда строку превращают в байты или байты в строку. Ошибка на этом шаге и даёт «кракозябры».

Строка → двоичные данные → файл:

Текст = "Номер;Сумма" + Символы.ПС + "125;4500,00";
ДД = ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8, Ложь);
ДД.Записать("C:\Выгрузка\реестр.csv");

Третий параметр управляет BOM — тремя служебными байтами EF BB BF в начале UTF-8 файла.

Кому отдаёте файлКодировкаBOM
Excel (открытие CSV двойным щелчком)UTF-8нужен, иначе кириллица ломается
Веб-сервис, JSON, XML для APIUTF-8обычно не нужен, часть парсеров на нём падает
Старые программы под Windows, банковские клиентыКодировкаТекста.ANSI (windows-1251)не бывает
Консоль Windows, bat-файлыКодировкаТекста.OEM (cp866)не бывает

Обратное преобразование — прочитать файл как текст:

ДД = Новый ДвоичныеДанные("C:\Загрузка\ответ.txt");
Текст = ПолучитьСтрокуИзДвоичныхДанных(ДД, КодировкаТекста.UTF8);

Если текст надо писать построчно, удобнее ЗаписьДанных поверх потока:

Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
Для Каждого Строка Из ТаблицаВыгрузки Цикл
    Запись.ЗаписатьСтроку(СтрШаблон("%1;%2", Строка.Номер, Формат(Строка.Сумма, "ЧДЦ=2; ЧГ=0")));
КонецЦикла;
Запись.Закрыть();

Числа в строку выгрузки всегда переводите через Формат(..., "ЧГ=0"). Без этого 4500 превратится в 4 500 с неразрывным пробелом, и принимающая программа не распознает сумму.

Буфер двоичных данных и системный буфер обмена

Слово «буфер» в 1С означает две разные вещи. Их часто путают.

БуферДвоичныхДанных — изменяемый массив байтов внутри 1С. Через него собирают файл из байтов «вручную»: заголовки, сигнатуры, бинарные форматы оборудования.

Буфер = Новый БуферДвоичныхДанных(4);
Буфер.Установить(0, 37);  // %
Буфер.Установить(1, 80);  // P
Буфер.Установить(2, 68);  // D
Буфер.Установить(3, 70);  // F
ДД = ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер);
ДД.Записать(ИмяФайла);

Обратный путь: ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ДД). Так удобно проверить сигнатуру файла: первые байты PDF — %PDF, ZIP и XLSX — PK.

Системный буфер обмена Windows — другое дело. Для создания файла на диске он не нужен. В последних версиях платформы для него появилось отдельное свойство глобального контекста СредстваБуфераОбмена. Для старых релизов обходной путь один: сохранить файл на диск и дать пользователю путь к нему или открыть каталог через НачатьЗапускПриложения.

Ошибки при записи файла и как их ловить

Запись на диск всегда может упасть: нет прав, каталога, места, файл открыт в Excel. Оборачивайте её в Попытка и пишите причину в журнал регистрации:

Попытка
    ДД.Записать(ИмяФайла);
Исключение
    Инфо = ИнформацияОбОшибке();
    ЗаписьЖурналаРегистрации("Выгрузка файлов", УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(Инфо));
    ВызватьИсключение СтрШаблон("Не удалось записать файл %1: %2",
        ИмяФайла, КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;
Диагностика: выберите симптом

Ещё три правила для надёжной записи:

  1. Закрывайте потоки в обеих ветках Попытка. Незакрытый ФайловыйПоток держит файл заблокированным, пока процесс жив.
  2. Удаляйте временные файлы и при ошибке. Иначе каталог временных файлов на сервере за месяц обрастает гигабайтами.
  3. Не собирайте имя файла из пользовательского ввода без чистки. Символы \ / : * ? " < > | в имени Windows запрещены, и запись упадёт.

Как убедиться, что файл записан целиком

Отсутствие исключения ещё не гарантирует, что файл годен. Для критичных выгрузок — реестров в банк, файлов для ФНС-сервисов, сканов договоров — сделайте контрольное чтение:

  1. Прочитайте записанный файл обратно: Проверка = Новый ДвоичныеДанные(ИмяФайла).
  2. Сравните размеры: Проверка.Размер() = ДД.Размер().
  3. Для полной уверенности сравните хеши. Объект ХешированиеДанных с типом ХешФункция.SHA256 считает хеш от исходных и от прочитанных данных; значения должны совпасть.

Такая проверка добавляет одно чтение с диска, зато ловит обрезанные файлы при переполнении диска или сбое сетевой папки.

Большие файлы: как не съесть память сервера

ДвоичныеДанные целиком живут в оперативной памяти. Файл на 1,5 ГБ, прочитанный в ДД, займёт 1,5 ГБ памяти рабочего процесса. Каждое преобразование — Base64, строка, копия — добавит ещё столько же.

Что делать на объёмах от сотен мегабайт:

  • 🔁 Копируйте поток в поток, не загружая весь файл в память: Источник.КопироватьВ(Приёмник).
  • 🧩 Читайте и пишите кусками через буфер фиксированного размера.
  • 📉 Избегайте лишних преобразований: строка Base64 на треть больше исходных байтов.
  • 🗄 Не храните крупные файлы в ХранилищеЗначения реквизита — база растёт, резервные копии тяжелеют. Храните их в томах на диске.

Копирование кусками по 1 МБ:

Источник = ФайловыеПотоки.ОткрытьДляЧтения(ИмяИсходного);
Приемник = Новый ФайловыйПоток(ИмяНового, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Буфер = Новый БуферДвоичныхДанных(1048576);
Пока Истина Цикл
    Прочитано = Источник.Прочитать(Буфер, 0, Буфер.Размер);
    Если Прочитано = 0 Тогда
        Прервать;
    КонецЕсли;
    Приемник.Записать(Буфер, 0, Прочитано);
КонецЦикла;
Приемник.Закрыть();
Источник.Закрыть();

Расход памяти здесь постоянный — около 1 МБ на буфер, сколько бы весил файл.

ПодходПамятьСкоростьГде применять
ДД.Записать()весь файлвысокаядо 100–200 МБ
КопироватьВ между потокаминебольшой внутренний буфервысокаябольшие файлы целиком
Цикл с БуферДвоичныхДанныхразмер буферавысокаянужна обработка по частям: подсчёт, проверка, прогресс
ЗаписьДанных.ЗаписатьСтрокуодна строкасредняятекстовые выгрузки на сотни тысяч строк

Тяжёлую выгрузку в рабочее время запускайте в фоновом задании, а не прямо из формы. Иначе пользователь ждёт с замершим окном, а при обрыве связи клиент-сервер операция прервётся на середине.

Частые вопросы

Можно ли записать двоичные данные в файл на сервере под Linux?
Да, метод тот же. Используйте путь в формате Linux, например /var/1c/exchange/file.pdf, и выдайте права на каталог пользователю, от имени которого работает сервер 1С.
Как узнать размер файла до записи?
Вызовите ДД.Размер() — метод вернёт число байтов. Для перевода в мегабайты разделите результат на 1048576.
Как получить двоичные данные из файла, который уже лежит на диске?
Конструктором Новый ДвоичныеДанные(ИмяФайла). Для очень больших файлов вместо этого откройте поток через ФайловыеПотоки.ОткрытьДляЧтения.
Как дописать данные в конец существующего файла, а не перезаписать его?
Откройте ФайловыйПоток с режимом РежимОткрытияФайла.Дописать и пишите в него. Метод ДД.Записать() всегда перезаписывает файл целиком.
Как упаковать несколько файлов в архив перед сохранением?
Запишите их во временный каталог и добавьте в архив объектом ЗаписьZipФайла. После записи архива временные файлы удалите.