Про файловые потоки: работа с любыми данными и в любом количестве

23.06.24

Разработка - Механизмы платформы 1С

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

На мой взгляд, механизм работы с данными через файловый поток в 1С недооценен: я редко где-то встречаю его реализацию, а запрос в Google или поиск по Инфостарт не выдает практически никаких результатов - если речь заходит о потоках, то, как правило, о ПотокахВПамяти, которые позволяют в некоторых ситуациях избежать создания временного файла при работе с данными

А ведь по своему опыту могу сказать, что узнав о файловом потоке лишь однажды, ты навсегда начинаешь смотреть на любой объект Записи/Чтения, инициализированный через путь к файлу, как на страшный моветон, вроде запроса в цикле или венгерские нотации

Чем же он так хорош?

  • Файловый поток позволяет читать и записывать файлы любого размера. В отличии от объекта ДвоичныеДанные, при использовании которого весь файл помещается в оперативную память, данный механизм обрабатывает данные частями, практически не напрягая железо
  • При помощи потока данные можно записывать кусками в цикле или даже дописывать их к информации, уже существующей в файле на данный момент

При этом, вся эта благодать поддерживается большинством записывающих/читающих объектов, работающих с двоичными данными по умолчанию: Запись/ЧтениеДанных, Запись/ЧтениJSON, Запись/ЧтениеТекста и др.

Но показать проще всего на примерах, некоторые из которых мы сейчас рассмотрим:

 

Запись данных любого размера

 

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

Запись в данном случае выглядит следующим образом
 

	РезультатЗапроса = Запрос.Выполнить();
	
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	
	Поток        = Новый ФайловыйПоток("C:/1.csv", РежимОткрытияФайла.Дописать); // Открываем файловый поток по пути
	ЗаписьТекста = Новый ЗаписьТекста(Поток);                                    // Передаем поток в запись текста
	
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		
		МассивДанных = Новый Массив;
		
		// Проходим все колонки и записываем значения в массив
		Для Каждого Колонка Из РезультатЗапроса.Колонки Цикл
			 МассивДанных.Добавить(ВыборкаДетальныеЗаписи[Колонка.Имя]);    
		КонецЦикла;
		
		// Соединяем массив в строку через запятые (формат CSV)
		СтрокаДанных = СтрСоединить(МассивДанных, ",");
		ЗаписьТекста.ЗаписатьСтроку(СтрокаДанных);
		
	КонецЦикла;
	
	ЗаписьТекста.Закрыть(); // Закрываем поток

 

Здесь идет выполнение запроса, текст которого остался за кадром. Далее мы, при помощи объекта ФайловыйПоток, открываем поток на основе пути к файлу. Для нас тут более всего интересен второй параметр - режим открытия файла, который может принимать следующие значения:

  • Дописать - открывает существующий файл и начинает запись с его конца или создает новый, в случае отсутствия
  • Обрезать - открывает существующий файл и очищает его, начиная запись с начала. Исключение, если файл не существует
  • Открыть - просто открывает существующий файл. Исключение, если файл не существует
  • ОткрытьИлиСоздать - то же, что и Обрезать, но создает новый файл, если он не существует
  • Создать - я так и не нашел отличия от ОткрытьИлиСоздать
  • СоздатьНовый - создает новый файл, исключение, если уже существует

Нас, в данном случае, интересует вариант Дописать, так как только он подразумевает сохранение данных, уже существующих в файле на момент записи туда новой информации.

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

Так как работать с потоком напрямую сложно и неудобно, нам необходим объект-помощник. В данном случае это ЗаписьТекста, но в зависимости от ситуации это также могут быть, например, ЗаписьДанных или ЗаписьJSON

Для записи текста мы формируем из полученных в запросе данных строку с запятыми в качестве разделителей - CSV формат, после чего записываем её в файл. Запись одной строки не создает практически никакой нагрузки на сервер - регулировать остается лишь размер выборки данных из базы. Если оперативы для нее хватает - писать можно хоть пока не закончится место на диске

Более жизненный вариант: те же данные но в формате JSON:

 

	РезультатЗапроса = Запрос.Выполнить();
	
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	
	Поток = Новый ФайловыйПоток("C:/2.json", РежимОткрытияФайла.Дописать); // Открываем файловый поток по пути
	JSON  = Новый ЗаписьJSON(); 
	JSON.ОткрытьПоток(Поток);
	
	JSON.ЗаписатьНачалоМассива();
	
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		
		СтруктураДанных = Новый Структура;
		
		// Проходим все колонки и записываем значения в структуру
		Для Каждого Колонка Из РезультатЗапроса.Колонки Цикл		
			Поле = Колонка.Имя;
			СтруктураДанных.Вставить(Поле, XMLСтрока(ВыборкаДетальныеЗаписи[Поле]));
		КонецЦикла;
		
		ЗаписатьJSON(JSON, СтруктураДанных);
		
	КонецЦикла;
	
	JSON.ЗаписатьКонецМассива();
	JSON.Закрыть();

 

Здесь вместо записи текста мы уже используем запись JSON: для того, чтобы JSON был валиден, мы добавляем дополнительно запись начала и конца массива ('[' и ']' в начале и конце файла), а потом, в цикле, при помощи функции ЗаписатьJSON записываем в файл сформированную структуру - функция сама определит, как это должно выглядеть в виде текста JSON, а также, что очень приятно, сама поставит запятую перед новой строкой, для разделения элементов JSON-массива при дописывании данных

 

 

Отдельно хочется выделить популярный сейчас формат NDJSON, который может использоваться как источник данных в некоторых БД. Для его формирования, в нашем случае, мы можем скомбинировать ЗаписьJSON и ЗаписьТекста, чтобы автоматические механизмы работы с JSON не наставили лишних запятых и скобок

 

    ФайловыйПоток = Новый ФайловыйПоток("C:/2.json", РежимОткрытияФайла.Дописать);
    ЗаписьТекста  = Новый ЗаписьТекста(ФайловыйПоток);
    
    Для Каждого Запрос Из МассивЗапросов Цикл
        
        РезультатЗапроса = Запрос.Выполнить();
        Выборка           = РезультатЗапроса.Выбрать();
        
        Пока Выборка.Следующий() Цикл
            
            СтруктураДанных = Новый Структура;
            
            // Проходим все колонки и записываем значения в структуру
            Для Каждого Колонка Из РезультатЗапроса.Колонки Цикл		
                Поле = Колонка.Имя;
                СтруктураДанных.Вставить(Поле, XMLСтрока(Выборка[Поле]));
            КонецЦикла;
        
            ЗаписьJSON    = Новый ЗаписьJSON();
            ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет);
            ЗаписьJSON.УстановитьСтроку(ПараметрыJSON);
            
            ЗаписатьJSON(ЗаписьJSON, СтруктураДанных);
            СтрокаJSON = ЗаписьJSON.Закрыть();
            
            ЗаписьТекста.ЗаписатьСтроку(СтрокаJSON);        
              
        КонецЦикла;
        
    КонецЦикла;
    
    ЗаписьТекста.Закрыть();

 

В данном случае мы проходим в цикле несколько запросов, формируя на каждый элемент выборки JSON-строку, которая в последствии записывается просто через объект ЗаписьТекста

Итоговый файл выглядит следующим образом:

 

{"Код":"JYLWG","Наименование":"Бесы","Дата":"1872 г.","НаСайте":"true","Слов":"194511"}
{"Код":"000000029","Наименование":"Милый друг","Дата":"1885 г.","НаСайте":"false","Слов":"0"}
{"Код":"000000030","Наименование":"Таинственный незнакомец","Дата":"1916 г.","НаСайте":"false","Слов":"0"}
{"Код":"0VR4J","Наименование":"Братья Карамазовы","Дата":"1880 г.","НаСайте":"true","Слов":"288434"}
{"Код":"UDUMD","Наименование":"Преступление и наказание","Дата":"1866 г.","НаСайте":"true","Слов":"168997"}
{"Код":"PW125","Наименование":"Дон Кихот","Дата":"1605 г.","НаСайте":"true","Слов":"345759"}
{"Код":"WA7J1","Наименование":"Критика чистого разума","Дата":"1781 г.","НаСайте":"true","Слов":"172208"}
...

 

Чтение из файла любого размера

 

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

    Поток = Новый ФайловыйПоток("C:/2.json", РежимОткрытияФайла.Открыть); // Открываем файловый поток по пути
	
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.ОткрытьПоток(Поток);
    
    Структура    = Новый Структура;
    МассивЧтения = Новый Массив;
	
    Пока ЧтениеJSON.Прочитать() Цикл
        
        Если ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.ИмяСвойства Тогда
            Имя = ЧтениеJSON.ТекущееЗначение;
            
        ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.Булево
            Или ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.Строка
            Или ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.Число Тогда
            
            Структура.Вставить(Имя, ЧтениеJSON.ТекущееЗначение);
            
        ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.КонецОбъекта Тогда
            
            МассивЧтения.Добавить(Структура);
            Структура = Новый Структура;
            
        КонецЕсли;
     
    КонецЦикла;

 

В данном случае запись идет в массив, так что, по итогу, все данные все равно окажутся в памяти. Но это для примера: в действительности, вместо добавления в массив, скорее всего будет запись в базу. Следовательно, память будет заниматься только в рамках одного объекта

 

Немного про HTTP-запросы

 

Часто проблема работы с большими объемами данных возникает при использовании HTTP-запросов - причем как во время создания своего запроса, так и при получении большого объема данных в ответе. У самих объектов HttpЗапрос и HttpОтвет есть, конечно, методы для установки и получения тела из потока, но нам это здесь не поможет, так как речь идет про потоки в памяти

Однако, вариант потокового чтения и записи в файловом потоке для тела Http-запроса/ответа есть. Заключается он в использовании функции УстановитьИмяФайлаТела (для запроса)...

 

    Запрос = Новый HTTPЗапрос("/");
    Запрос.УстановитьИмяФайлаТела("C:/2.json");

 

...и параметра ИмяВыходногоФайла у функций отправки запроса Http-соединения (для ответа)

 

    Соединение = Новый HTTPСоединение("exemple.com");
    Соединение.ВызватьHTTPМетод("GET", Запрос, "C:/reponse.json");

 

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

В общем говоря, использование файловых потоков позволяет очень сильно сократить потребление оперативной памяти при работе с файлами - как на чтение, так и на запись. В частности, они идеально подходят для реализации различных обменов между системами с использованием файлов стандартных форматов, вроде CSV, JSON или XML

 

Спасибо за внимание!

 

 

 Мой GitHub:     https://gitub.com/Bayselonarrend 
 Лицензия MIT:   https://mit-license.org

Поток запись большие данные json файловыйпоток

См. также

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    5935    dsdred    16    

80

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Бесплатно (free)

Все мы используем массивы в своем коде. Это один из первых объектов, который дают ученикам при прохождении обучения программированию. Но умеем ли мы ими пользоваться? В этой статье я хочу показать все методы массива, а также некоторые фишки в работе с массивами.

24.01.2024    17612    YA_418728146    26    

71

Перенос данных 1C Механизмы платформы 1С Системный администратор Программист Стажер Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    11207    dsdred    44    

130

Механизмы платформы 1С Программист Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    23745    SeiOkami    48    

135

Механизмы платформы 1С Системный администратор Платформа 1С v8.3 Бесплатно (free)

Начиная с версии платформы 8.3.22 1С снимает стандартные блокировки БД на уровне страниц. Делаем рабочий скрипт, как раньше.

14.09.2023    18806    human_new    27    

80

WEB-интеграция Универсальные функции Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    14721    YA_418728146    7    

166

Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Внешние компоненты для 1С можно разработывать очень просто, пользуясь всеми преимуществами языка Rust - от безопасности и кроссплатформенности до удобного менеджера библиотек.

20.08.2023    8370    sebekerga    55    

104
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. Поручик 4692 24.06.24 08:21 Сейчас в теме
Спасибо.
DrAku1a; bayselonarrend; +2 Ответить
2. period 7 24.06.24 08:50 Сейчас в теме
Не совсем понимаю логики, поэтому переспрошу: разве в целях производительности мы не должны, наоборот, избавиться по возможности от файловых операций и держать максимум оперативной информации в оперативной памяти? Опять же, скорее всего, все эти операции операционная система закэшит в памяти, поэтому не так страшно. Но сэкономит ли это саму память в конечном итоге?
3. bayselonarrend 2087 24.06.24 08:58 Сейчас в теме
(2)
Не совсем понимаю логики, поэтому переспрошу: разве в целях производительности мы не должны, наоборот, избавиться по возможности от файловых операций и держать максимум оперативной информации в оперативной памяти?


Должны, пока количество оперативной информации не превысит количество доступной оперативной памяти. При попытке, например, записать несколько миллионов строк в файл за раз, мы получим вылет по памяти либо уже на выборке данных из базы, либо на самой записи в файл. То же и с чтением: попытка открыть файл в несколько десятков ГБ через, например, двоичные данные, приведет к схожему результату

В случае же с потоком, мы можем разбить выборку на несколько запросов или даже открывать файл на запись несколько раз. При этом данные туда будут дописываться, без необходимости выгружать уже существующие в оперативную память. Чтение же вообще будет тратить памяти в размере одного блока информации за раз (json объекта, например)

Разница примерно как у РезультатЗапроса.Выбрать() и РезультатЗапроса.Выгрузить(), только с данными файла
17. DrAku1a 1745 26.06.24 12:32 Сейчас в теме
(3) Всё правильно сказано, только в начале статьи
узнав о файловом потоке лишь однажды, ты навсегда начинаешь смотреть на любой объект Записи/Чтения, инициализированный через путь к файлу, как на страшный моветон, вроде запроса в цикле или венгерские нотации

Важно понимать, что бывают ситуации, когда и запрос в цикле, и РезультатЗапроса.Выгрузить() и типовые операции чтения/записи файлов единым блоком - вполне рабочие, т.к. в итоге приводят к незначительной нагрузке. И эти ситуации бывают довольно часто.
Важно знать и помнить про то, что когда работа идёт с большим блоком информации - нужны иные подходы и инструменты, и в этом плане Ваша статья очень полезна.
Прикрепленные файлы:
Патриот; +1 Ответить
4. KirillZ44 12 24.06.24 11:15 Сейчас в теме
Большое спасибо, очень познавательно.
bayselonarrend; +1 Ответить
5. kamisov 216 24.06.24 15:09 Сейчас в теме
Не понял... Выложил статью только вчера, уже целых 78 плюсов, и никто не увидел?
Прикрепленные файлы:
bayselonarrend; +1 Ответить
6. bayselonarrend 2087 24.06.24 15:10 Сейчас в теме
(5)Самое глупое, что я это правил, но не сохранил похоже. Спасибо
7. bayselonarrend 2087 24.06.24 15:20 Сейчас в теме
(5)
Не понял... Выложил статью только вчера, уже целых 78 плюсов, и никто не увидел?


Выглядит конечно странно со стороны, но а что я сделаю
8. kamisov 216 24.06.24 16:19 Сейчас в теме
(7) я про то что 78 человек не просто пролистали, а еще и плюс ткнули. И никто ведь не обратил внимание. Странно!
9. qwinter 683 24.06.24 16:41 Сейчас в теме
(8) Многие добавляют интересные длинные статьи в избранное, что бы прочитать позже.
10. kuzyara 2090 25.06.24 04:54 Сейчас в теме
Как извлечь файлы из zip-архива в поток? записать из потока в zip-архив?
11. siamagic 25.06.24 05:44 Сейчас в теме
ЗаписьТекста всегда позволяла дописывать, что изменил файловый поток в вашем примере?
Ничего.
13. uno-c 264 25.06.24 14:36 Сейчас в теме
(11) Если открыть ЗаписьТекста через ФайловыйПоток - то можно управлять параметром ЗаписатьBOM и по-умолчанию BOM не будет добавляться. Если ЗаписьТекста инициализовать напрямую через файл (без посредника в виде ФайловыйПоток) то, например, в UTF-8 запись BOM добавится без вариантов.

Вывод: пример из статьи все-таки кое-что изменил, а именно убрал три первых байта из csv-файла. При записи как в примере через Поток - в итоговом файле будет отсутствовать маркер последовательности байтов UTF-8 (EF BB BF)
16. siamagic 25.06.24 21:21 Сейчас в теме
(13) В статье эта информация отсутствие, как и информация по управлению размером буфера.
12. uno-c 264 25.06.24 14:28 Сейчас в теме
узнав о файловом потоке лишь однажды, ты навсегда начинаешь смотреть на любой объект Записи/Чтения, инициализированный через путь к файлу, как на страшный моветон
Файловый поток - это объект, предназначенный для записи/чтения данных. Инициализируется Файловый поток через путь к файлу. Выходит, файловый поток - тоже страшный моветон.

Не понятно, чем две строки
Поток = Новый ФайловыйПоток("C:/1.csv", РежимОткрытияФайла.Дописать);
ЗаписьТекста = Новый ЗаписьТекста(Поток);
"выгоднее", чем одна строка
ЗаписьТекста = Новый ЗаписьТекста("C:/1.csv",,,Истина);
Разве что в возможности управления параметром ЗаписатьBOM.

Или эти три строки
Поток = Новый ФайловыйПоток("C:/2.json", РежимОткрытияФайла.Дописать);
JSON  = Новый ЗаписьJSON(); 
JSON.ОткрытьПоток(Поток);
непонятно, чем они "выгоднее", чем две
JSON  = Новый ЗаписьJSON(); 
JSON.ОткрытьФайл("C:/2.json");


По чтению то же самое, лишняя строчка, лишний объект, непонятно какие выгоды от файлового потока.

ЗаписьТекста.Закрыть(); // Закрываем поток
Здесь закрывается не поток, а ЗаписьТекста. Сам Поток остается открытым. Например, после ЗаписьТекста.Закрыть() можно ниже дописать, и оно сработает:
Буфер = ПолучитьБуферДвоичныхДанныхИзСтроки("BinaryDataBuffer");
Поток.Записать(Буфер,0,16);
21. mikukrnet 182 23.09.24 17:14 Сейчас в теме
(12) Разница под капотом. Если вы натравите filebeat на свои логи - то через ЗаписьТекста вы туда ничего не запишите. А через поток - легко
18. Serg2000mr 670 26.06.24 17:22 Сейчас в теме
(0) Есть еще МенеджерФайловыхПотоков, было бы здорово, если бы рассказали и про него.
19. pstrig 27.06.24 08:37 Сейчас в теме
Интересно, возможно ли применить потоки при чтении/записи xlsx файлов?
20. n_mezentsev 57 04.07.24 14:32 Сейчас в теме
А как насчет конкурирующей записи / чтения в/из Файлового потока? Для чего нужны ДоступноЧтение и ДоступнаЗапись?
Оставьте свое сообщение