Запись ДвоичныхДанных на диск в 1С 8.3: от одной строки кода до потоков
Содержание
Файл из двоичных данных в 1С создаётся одной строкой: ДвоичныеДанные.Записать("C:\Выгрузка\счет.pdf"). Метод берёт байты из объекта и пишет их на диск как есть, без преобразований. Если файл с таким именем уже есть, он перезаписывается.
Сложности начинаются дальше. Файл появляется не на том компьютере, текст открывается «кракозябрами», выгрузка на 2 ГБ съедает память сервера. Для каждого из этих случаев есть свой приём, и все они работают на платформе 1С:Предприятие 8.3. Тема общая для платформы и не зависит от конфигурации: код одинаково работает в Бухгалтерии, ЗУП, УТ и самописных базах.
Откуда берутся двоичные данные, потоки и буфер
ДвоичныеДанные — неизменяемый объект: набор байтов целиком в памяти. Изменить байт внутри него нельзя, можно только получить новый объект. Для пошаговой работы с байтами в платформе 8.3.9 появились потоки и буфер.
| Объект | Что это | Когда нужен |
|---|---|---|
ДвоичныеДанные | готовый набор байтов в памяти | файл до сотен мегабайт, простая запись |
БуферДвоичныхДанных | изменяемый массив байтов фиксированного размера | правка отдельных байтов, чтение кусками |
ФайловыйПоток | поток, привязанный к файлу на диске | запись по частям, большие файлы |
ПотокВПамяти | поток в оперативной памяти | сборка файла из частей перед выгрузкой |
ЗаписьДанных | «писатель» поверх потока | запись строк, чисел, байтов в нужной кодировке |
Где обычно лежат двоичные данные, которые нужно превратить в файл:
- 📦 реквизит типа
ХранилищеЗначения—ДД = Объект.Файл.Получить(); - 🌐 ответ HTTP-сервиса —
ДД = Ответ.ПолучитьТелоКакДвоичныеДанные(); - 🔤 строка Base64 из XML или JSON —
ДД = Base64Значение(Строка); - 📎 присоединённые файлы в типовых конфигурациях на БСП — через
РаботаСФайлами.ДвоичныеДанныеФайла(Ссылка); - 🗂 временное хранилище после загрузки файла пользователем —
ДД = ПолучитьИзВременногоХранилища(Адрес).
Перед записью проверьте тип: ТипЗнч(ДД) = Тип("ДвоичныеДанные"). Хранилище значения без .Получить() или пустое значение Неопределено — самая частая причина ошибки «Метод объекта не обнаружен (Записать)».
Как записать файл из двоичных данных на диск: пошагово
Способ 1. Метод Записать
Самый короткий путь. Подходит для 95% бытовых задач: сохранить вложение, выгрузить картинку, записать PDF.
ДД = Base64Значение(СтрокаBase64);
ИмяФайла = "C:\Выгрузка\счет_125.pdf";
ДД.Записать(ИмяФайла);
Порядок действий:
- Получите объект
ДвоичныеДанныеиз источника. - Соберите полное имя файла: каталог, имя, расширение.
- Убедитесь, что каталог существует. Если нет — создайте его через
СоздатьКаталог(Путь). - Вызовите
ДД.Записать(ИмяФайла). - Проверьте результат:
Новый Файл(ИмяФайла).Существует().
Способ 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 для API | UTF-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",
ИмяФайла, КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;
Ещё три правила для надёжной записи:
- Закрывайте потоки в обеих ветках
Попытка. НезакрытыйФайловыйПотокдержит файл заблокированным, пока процесс жив. - Удаляйте временные файлы и при ошибке. Иначе каталог временных файлов на сервере за месяц обрастает гигабайтами.
- Не собирайте имя файла из пользовательского ввода без чистки. Символы
\ / : * ? " < > |в имени Windows запрещены, и запись упадёт.
Как убедиться, что файл записан целиком
Отсутствие исключения ещё не гарантирует, что файл годен. Для критичных выгрузок — реестров в банк, файлов для ФНС-сервисов, сканов договоров — сделайте контрольное чтение:
- Прочитайте записанный файл обратно:
Проверка = Новый ДвоичныеДанные(ИмяФайла). - Сравните размеры:
Проверка.Размер() = ДД.Размер(). - Для полной уверенности сравните хеши. Объект
ХешированиеДанныхс типомХешФункция.SHA256считает хеш от исходных и от прочитанных данных; значения должны совпасть.
Такая проверка добавляет одно чтение с диска, зато ловит обрезанные файлы при переполнении диска или сбое сетевой папки.
Большие файлы: как не съесть память сервера
ДвоичныеДанные целиком живут в оперативной памяти. Файл на 1,5 ГБ, прочитанный в ДД, займёт 1,5 ГБ памяти рабочего процесса. Каждое преобразование — Base64, строка, копия — добавит ещё столько же.
Что делать на объёмах от сотен мегабайт:
- 🔁 Копируйте поток в поток, не загружая весь файл в память:
Источник.КопироватьВ(Приёмник). - 🧩 Читайте и пишите кусками через буфер фиксированного размера.
- 📉 Избегайте лишних преобразований: строка Base64 на треть больше исходных байтов.
- 🗄 Не храните крупные файлы в
ХранилищеЗначенияреквизита — база растёт, резервные копии тяжелеют. Храните их в томах на диске.
Копирование кусками по 1 МБ:
Источник = ФайловыеПотоки.ОткрытьДляЧтения(ИмяИсходного);
Приемник = Новый ФайловыйПоток(ИмяНового, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Буфер = Новый БуферДвоичныхДанных(1048576);
Пока Истина Цикл
Прочитано = Источник.Прочитать(Буфер, 0, Буфер.Размер);
Если Прочитано = 0 Тогда
Прервать;
КонецЕсли;
Приемник.Записать(Буфер, 0, Прочитано);
КонецЦикла;
Приемник.Закрыть();
Источник.Закрыть();
Расход памяти здесь постоянный — около 1 МБ на буфер, сколько бы весил файл.
| Подход | Память | Скорость | Где применять |
|---|---|---|---|
ДД.Записать() | весь файл | высокая | до 100–200 МБ |
КопироватьВ между потоками | небольшой внутренний буфер | высокая | большие файлы целиком |
Цикл с БуферДвоичныхДанных | размер буфера | высокая | нужна обработка по частям: подсчёт, проверка, прогресс |
ЗаписьДанных.ЗаписатьСтроку | одна строка | средняя | текстовые выгрузки на сотни тысяч строк |
Тяжёлую выгрузку в рабочее время запускайте в фоновом задании, а не прямо из формы. Иначе пользователь ждёт с замершим окном, а при обрыве связи клиент-сервер операция прервётся на середине.
Частые вопросы
Можно ли записать двоичные данные в файл на сервере под Linux?
/var/1c/exchange/file.pdf, и выдайте права на каталог пользователю, от имени которого работает сервер 1С.Как узнать размер файла до записи?
ДД.Размер() — метод вернёт число байтов. Для перевода в мегабайты разделите результат на 1048576.Как получить двоичные данные из файла, который уже лежит на диске?
Новый ДвоичныеДанные(ИмяФайла). Для очень больших файлов вместо этого откройте поток через ФайловыеПотоки.ОткрытьДляЧтения.Как дописать данные в конец существующего файла, а не перезаписать его?
ФайловыйПоток с режимом РежимОткрытияФайла.Дописать и пишите в него. Метод ДД.Записать() всегда перезаписывает файл целиком.Как упаковать несколько файлов в архив перед сохранением?
ЗаписьZipФайла. После записи архива временные файлы удалите.