ДвоичныеДанные, буферы и потоки: как 1С 8.3 читает файл байт за байтом
Содержание
Чтобы прочитать файл как двоичные данные в 1С, достаточно одной строки: Данные = Новый ДвоичныеДанные("C:\Обмен\счет.pdf");. Платформа загрузит файл целиком в память, и дальше его можно записать в реквизит, отправить по HTTP, перевести в Base64 или посчитать хеш.
Этого хватает для файлов до десятков мегабайт. Для гигабайтных выгрузок, разбора заголовков и побайтовой работы в 1С:Предприятии 8.3 есть объекты БуферДвоичныхДанных, ЧтениеДанных и файловые потоки. Ниже — как и когда применять каждый, с рабочим кодом на встроенном языке.
Всё описанное относится к платформе, а не к конкретной конфигурации: код одинаково работает в БП, ЗУП, УТ или своей разработке. Объекты буферов и потоков появились в платформе 8.3.9, поэтому база должна работать в режиме совместимости не ниже этой версии.
Что хранит объект ДвоичныеДанные и чем он ограничен
ДвоичныеДанные — неизменяемый набор байтов. Создали объект — поменять в нём отдельный байт уже нельзя. Можно только прочитать, скопировать или собрать новый.
Что умеет объект сам по себе:
- 📏
Размер()— размер в байтах; - 💾
Записать(ИмяФайла)— сохранить на диск; - 🔁
ОткрытьПотокДляЧтения()— получить поток и читать его по частям; - 🧳 передаваться между клиентом и сервером как параметр и храниться во временном хранилище.
Главное ограничение — память. Конструктор Новый ДвоичныеДанные(ИмяФайла) читает файл полностью. Файл на 2 ГБ займёт 2 ГБ в рабочем процессе сервера, а в 32-битном клиенте такой объём просто не поместится.
Сравнение инструментов для чтения:
| Инструмент | Когда брать | Ограничение |
|---|---|---|
Новый ДвоичныеДанные(Имя) | Файл целиком: вложение, скан, выгрузка до ~50 МБ | Весь файл в памяти |
БуферДвоичныхДанных | Нужно читать и менять отдельные байты, числа | Тоже в памяти, зато изменяемый |
ЧтениеДанных | Разбор формата: заголовок, записи фиксированной длины, строки | Только последовательное чтение вперёд |
ФайловыйПоток | Большой файл, чтение кусками, переход по смещению | Код длиннее, поток нужно закрывать |
Если файл текстовый и нужен как строка, двоичные данные не всегда нужны. Для построчного чтения CSV или лога удобнее ЧтениеТекста — оно сразу работает со строками и кодировкой.
Как прочитать файл в двоичные данные: базовый код
Минимальный рабочий пример на сервере:
&НаСервереБезКонтекста
Функция ПрочитатьФайл(ИмяФайла)
Файл = Новый Файл(ИмяФайла);
Если Не Файл.Существует() Тогда
ВызватьИсключение СтрШаблон("Файл не найден: %1", ИмяФайла);
КонецЕсли;
Данные = Новый ДвоичныеДанные(ИмяФайла);
Сообщить(СтрШаблон("Прочитано байт: %1", Формат(Данные.Размер(), "ЧГ=0")));
Возврат Данные;
КонецФункции
Порядок действий такой:
- Проверить, что файл есть:
Новый Файл(Имя).Существует(). - Создать объект
ДвоичныеДанные, передав полный путь. - Использовать результат: сохранить, отправить, преобразовать.
Где лежит файл: на сервере или у пользователя
Самая частая путаница. Код в процедуре &НаСервере ищет файл на компьютере сервера 1С, а не у пользователя. Путь C:\Users\Иванов\Desktop\счет.pdf на сервере, скорее всего, не существует.
Если файл выбирает пользователь, его сначала передают на сервер:
&НаКлиенте
Асинх Процедура ЗагрузитьФайл(Команда)
Параметры = Новый ПараметрыДиалогаПомещенияФайлов;
Параметры.Фильтр = "PDF (*.pdf)|*.pdf";
Описание = Ждать ПоместитьФайлНаСерверАсинх(, , , Параметры, УникальныйИдентификатор);
Если Описание = Неопределено Или Описание.ПомещениеФайлаОтменено Тогда
Возврат;
КонецЕсли;
ОбработатьНаСервере(Описание.Адрес);
КонецПроцедуры
&НаСервере
Процедура ОбработатьНаСервере(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
// Данные — объект ДвоичныеДанные
КонецПроцедуры
Асинхронные методы с Асинх/Ждать есть в платформе 8.3.18 и новее. В более ранних релизах ту же задачу решает НачатьПомещениеФайлаНаСервер с описанием оповещения. Этот способ работает и в веб-клиенте, где прямого доступа к диску нет.
Чтение по пути на сервере выполняется от имени пользователя ОС, под которым запущена служба 1С (часто USR1CV8). У него должны быть права на сетевую папку, иначе будет ошибка доступа, хотя у вас в проводнике файл открывается.
Чтение файла по байтам: буфер и ЧтениеДанных
Когда нужно не просто «взять файл», а разобрать его содержимое — проверить формат, достать число из заголовка, прочитать записи фиксированной длины, — подключают буфер и объект чтения.
Проверка сигнатуры файла
Расширение врёт: пользователь может переименовать .docx в .pdf. Надёжнее проверить первые байты. Читаем только 8 байт, а не весь файл:
Функция ТипФайлаПоСигнатуре(ИмяФайла)
Чтение = Новый ЧтениеДанных(ИмяФайла);
Результат = Чтение.Прочитать(8);
Чтение.Закрыть();
Буфер = Результат.ПолучитьБуферДвоичныхДанных();
Если Буфер.Размер < 4 Тогда
Возврат "Неизвестно";
КонецЕсли;
Б0 = Буфер.Получить(0);
Б1 = Буфер.Получить(1);
Б2 = Буфер.Получить(2);
Б3 = Буфер.Получить(3);
Если Б0 = 37 И Б1 = 80 И Б2 = 68 И Б3 = 70 Тогда // %PDF
Возврат "PDF";
ИначеЕсли Б0 = 137 И Б1 = 80 И Б2 = 78 И Б3 = 71 Тогда // 0x89 PNG
Возврат "PNG";
ИначеЕсли Б0 = 255 И Б1 = 216 И Б2 = 255 Тогда // FF D8 FF
Возврат "JPEG";
ИначеЕсли Б0 = 80 И Б1 = 75 И Б2 = 3 И Б3 = 4 Тогда // PK — ZIP, DOCX, XLSX
Возврат "ZIP";
КонецЕсли;
Возврат "Неизвестно";
КонецФункции
Сигнатуры, которые пригодятся чаще всего:
| Формат | Первые байты (hex) | Десятичные |
|---|---|---|
25 50 44 46 | 37 80 68 70 | |
| PNG | 89 50 4E 47 | 137 80 78 71 |
| JPEG | FF D8 FF | 255 216 255 |
| ZIP, DOCX, XLSX | 50 4B 03 04 | 80 75 3 4 |
Чтение чисел из заголовка
У PNG ширина и высота картинки лежат по смещениям 16 и 20 как 4-байтные целые со старшим байтом впереди. Метод буфера ПрочитатьЦелое32 берёт позицию и порядок байтов:
Чтение = Новый ЧтениеДанных(ИмяФайла);
Буфер = Чтение.Прочитать(24).ПолучитьБуферДвоичныхДанных();
Чтение.Закрыть();
Ширина = Буфер.ПрочитатьЦелое32(16, ПорядокБайтов.BigEndian);
Высота = Буфер.ПрочитатьЦелое32(20, ПорядокБайтов.BigEndian);
Сообщить(СтрШаблон("%1 x %2 пикселей", Формат(Ширина, "ЧГ=0"), Формат(Высота, "ЧГ=0")));
Порядок байтов — частая ловушка. Форматы Windows (BMP, WAV) пишут числа младшим байтом вперёд (ПорядокБайтов.LittleEndian), сетевые протоколы и PNG — старшим (ПорядокБайтов.BigEndian). Перепутаете — вместо 800 получите 537 067 520.
Последовательное чтение ЧтениеДанных
У объекта ЧтениеДанных есть методы, которые сдвигают позицию сами: ПрочитатьБайт(), ПрочитатьЦелое16(), ПрочитатьЦелое32(), ПрочитатьСимволы(Количество), ПрочитатьСтроку(), Пропустить(Количество). Это удобно для файлов из записей одинаковой длины: прочитали поле, перешли к следующему, без ручного подсчёта смещений.
Кодировку и порядок байтов задают в конструкторе: Новый ЧтениеДанных(ИмяФайла, КодировкаТекста.UTF8, ПорядокБайтов.LittleEndian).
Большие файлы: потоковое чтение по частям
Если файл весит сотни мегабайт или больше, держать его в памяти целиком нельзя. Читаем кусками фиксированного размера через ФайловыйПоток. Память занимает только один кусок.
Пример: считаем SHA-256 большой выгрузки, читая по 1 МБ.
Функция ХешБольшогоФайла(ИмяФайла)
РазмерКуска = 1024 * 1024;
Буфер = Новый БуферДвоичныхДанных(РазмерКуска);
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Поток = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Попытка
Пока Истина Цикл
Прочитано = Поток.Прочитать(Буфер, 0, РазмерКуска);
Если Прочитано = 0 Тогда
Прервать;
КонецЕсли;
Если Прочитано = РазмерКуска Тогда
Кусок = Буфер;
Иначе
Кусок = Буфер.Прочитать(0, Прочитано);
КонецЕсли;
Хеш.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Кусок));
КонецЦикла;
Поток.Закрыть();
Исключение
Поток.Закрыть();
ВызватьИсключение;
КонецПопытки;
Возврат НРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хеш.ХешСумма));
КонецФункции
Что в этом коде принципиально:
- 🔒 поток закрывается и при успехе, и при ошибке — иначе файл останется заблокированным до завершения рабочего процесса;
- ✂️ последний кусок обычно короче буфера, поэтому берём из буфера ровно
Прочитанобайт, а не весь буфер с хвостом от прошлой итерации; - 🧮 размер куска 1 МБ — разумный компромисс: меньше — много вызовов, больше — лишняя память.
Для одной задачи «хеш файла» есть короткий путь: Хеш.ДобавитьФайл(ИмяФайла) — платформа сама прочитает файл по частям. Ручной цикл нужен, когда с каждым куском делается что-то своё: отправка частями, поиск подстроки, копирование с прогрессом.
Переход по смещению
Поток умеет прыгать в нужную позицию без чтения всего, что до неё:
Поток = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Поток.Перейти(Поток.Размер() - 1024, ПозицияВПотоке.Начало); // последние 1024 байта
Буфер = Новый БуферДвоичныхДанных(1024);
Прочитано = Поток.Прочитать(Буфер, 0, 1024);
Поток.Закрыть();
Так читают «хвост» лога или служебную запись в конце архива. Для файла меньше 1024 байт сначала проверьте Поток.Размер(), иначе смещение уйдёт в минус.
Преобразование двоичных данных в строку, Base64 и другие типы 1С
Прочитанные байты почти всегда нужно во что-то превратить. Готовые функции глобального контекста:
| Задача | Функция |
|---|---|
| Байты → строка в кодировке | ПолучитьСтрокуИзДвоичныхДанных(Данные, "UTF-8") |
| Строка → байты | ПолучитьДвоичныеДанныеИзСтроки(Строка, "UTF-8") |
| Байты → Base64 | ПолучитьBase64СтрокуИзДвоичныхДанных(Данные) |
| Base64 → байты | ПолучитьДвоичныеДанныеИзBase64Строки(Строка) |
| Байты → hex | ПолучитьHexСтрокуИзДвоичныхДанных(Данные) |
| Байты → буфер (для правки) | ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Данные) |
| Буфер → байты | ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер) |
В старом коде встречаются Base64Строка() и Base64Значение(). Они работают, но в новых модулях лучше брать функции из таблицы: у них симметричные имена и есть варианты для буферов.
Кодировка при переводе в строку
Файл из старой учётной системы часто сохранён в Windows-1251. Если прочитать его как UTF-8, вместо текста получатся «кракозябры». Кодировку указывают явно: ПолучитьСтрокуИзДвоичныхДанных(Данные, "windows-1251"). Без параметра платформа считает текст UTF-8.
Хранение в базе
Двоичные данные нельзя положить в реквизит напрямую. Для этого есть тип ХранилищеЗначения, и при создании можно включить сжатие:
Объект.ФайлСкана = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
Обратно: Данные = Объект.ФайлСкана.Получить();. Сжатие 9 — максимальное. Для PDF и JPEG, которые уже сжаты, выигрыш мал, для XML и TXT — в разы.
Обработка ошибок при чтении файла
Любое чтение с диска оборачивают в Попытка … Исключение. Причины сбоя внешние: файл удалили, сеть отвалилась, антивирус держит блокировку.
Попытка
Данные = Новый ДвоичныеДанные(ИмяФайла);
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Обмен.ЧтениеФайла", УровеньЖурналаРегистрации.Ошибка, , ИмяФайла,
ПодробноеПредставлениеОшибки(Инфо));
ВызватьИсключение КраткоеПредставлениеОшибки(Инфо);
КонецПопытки;
Подробности идут в журнал регистрации для администратора, пользователю — короткий текст без стека.
Практические примеры: файлы в обменах и интеграциях
Отправка файла в HTTP-запросе
Данные = Новый ДвоичныеДанные(ИмяФайла);
Запрос = Новый HTTPЗапрос("/api/upload");
Запрос.Заголовки.Вставить("Content-Type", "application/pdf");
Запрос.УстановитьТелоИзДвоичныхДанных(Данные);
Соединение = Новый HTTPСоединение("example.ru", , , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния <> 200 Тогда
ВызватьИсключение СтрШаблон("Сервер вернул код %1", Формат(Ответ.КодСостояния, "ЧГ=0"));
КонецЕсли;
Обратное направление — Ответ.ПолучитьТелоКакДвоичныеДанные(). Для больших скачиваний у метода Получить есть параметр с именем выходного файла: ответ пишется сразу на диск и в память не грузится.
Файл внутри JSON или XML
Многие API принимают файл полем JSON. Байты переводят в Base64 и вставляют строкой:
Тело = Новый Структура;
Тело.Вставить("fileName", "act.pdf");
Тело.Вставить("content", ПолучитьBase64СтрокуИзДвоичныхДанных(Данные));
Base64 увеличивает объём примерно на треть: файл на 3 МБ превратится в строку около 4 МБ. Учтите это в лимитах принимающей стороны.
Сохранение у пользователя
Чтобы отдать файл с сервера на компьютер пользователя, двоичные данные помещают во временное хранилище и на клиенте вызывают получение файла:
Адрес = ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
Дальше на клиенте — ПолучитьФайлССервераАсинх (8.3.18 и новее) или НачатьПолучениеФайлаССервера в более ранних релизах.
Присоединённые файлы в типовых конфигурациях
В БП, ЗУП, УТ и других решениях на БСП файлы к документам хранятся через подсистему «Работа с файлами». Писать свой реквизит с хранилищем значения там не нужно: двоичные данные файла получают функцией общего модуля РаботаСФайлами.ДвоичныеДанныеФайла(Файл). Так код не зависит от того, где лежит файл — в базе или в томах на диске.