Асинхронное чтение файла




На этом уроке

  1. Познакомимся с файловой системой.
  2. Поговорим о кодировке.
  3. Рассмотрим класс Buffer.
  4. Узнаем, что такое чтение файла.
  5. Выясним, что такое запись файла.
  6. Узнаем, что такое поток.
  7. Рассмотрим виды потоков.
  8. Ознакомимся с чтением и записью файла через поток.
  9. Поговорим о модуле Stream.

 

 

Оглавление

Теория урока

Файловая система

Что такое кодировка

Класс Buffer

Работа с файлами

Чтение файла

Запись файла

Потоки

Поток на чтение

Поток на запись

Поток на чтение и запись

Практическое задание

Глоссарий

Дополнительные материалы

Используемые источники

Теория урока

Файловая система

Файловая система — это порядок, который определяет способ организации, хранения и именования данных (файлов) на цифровых носителях информации.

Обычно с этим понятием сталкиваются при форматировании флешки или SD-карты, установке операционной системы или восстановлении данных.

Файловая система отвечает за всё, что касается работы с файлами — присвоение имён, интерфейс работы с файлами для приложений, хранение параметров файлов и другое.

Таким образом, когда мы работаем с файлами на своём компьютере, то взаимодействуем с файловой системой.

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

В Node.js для работы с файловой системой предназначен стандартный модуль fs. Но, прежде чем перейти к изучению взаимодействия с файлами в Node.js, познакомимся с кодировкой.

Что такое кодировка

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

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

Как понять, какой набор единиц и нулей соответствует, например, букве «б»? Ведь это не число, которое можно перевести из его системы счисления в двоичную. Этот аспект и определяет кодировка.

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

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

Сегодня наиболее распространена кодировка UTF-8. Она поддерживается практически всеми редакторами. Если у нас есть файл, и мы не знаем, какая у него кодировка — сначала используем UTF-8.

Подробнее о кодировках — в дополнительных материалах в конце урока.

Класс Buffer

Перед тем как перейти к работе с файлами, поговорим о классе Buffer.

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

Компьютер умеет работать только с 0 и 1, любые типы данных представляются в этом виде.

О потоках мы поговорим чуть позже. Здесь только кратко упомянем, что это некая последовательность данных, которые перемещаются из одного места в другое. Главная идея потоков — возможность работать с данными «порционно», оптимизируя, таким образом, взаимодействие с памятью.

Работа с потоками двоичных данных, для которой предназначается класс Buffer, заключается в обработке перемещения данных файлов, например, при их чтении для дальнейшей обработки и передачи.

Что именно делает Buffer?

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

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

Проведём аналогию с залом ожидания аэропорта — пассажиры туда прибывают, а оттуда убывают. Там они ожидают рейса или ждут пересадки.

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

Работа с файлами

Теперь после краткого экскурса в кодировку текста в файлах, а также в класс Buffer перейдём к работе с ними. Для этого в Node.js есть стандартный модуль fs. Он позволяет работать с файловой системой — создавать и удалять директории и файлы, читать файлы, изменять их. Особенность модуля в том, что многие из операций имеют два варианта выполнения — синхронный и асинхронный. У синхронных методов в названии есть слово Sync. Асинхронные варианты функций после выполнения операции вызывают callback.

Модуль fs подключается в файл через уже знакомую нам функцию require.

const fs = require('fs');

Чтение файла

Допустим, нам надо прочитать файл логов с сервера. Файл назовём access.log и запишем в него следующие данные:

127.0.0.1 - - [30/Jan/2021:11:10:15 -0300] "GET /sitemap.xml HTTP/1.1" 200 0 "-" "curl/7.47.0"

 

В модуле fs для чтения файла есть два метода:

  1. readFile(path[, options], callback) — асинхронное чтение файла. По завершении чтения вызывается переданный в метод callback, где и будут доступны считанные данные.
  2. readFileSync(path[, options]) — синхронное чтение файла. Возвращает данные, полученные при чтении. Могут быть представлены либо строкой либо специальным классом Buffer, о нём поговорим немного позже.

Рассмотрим параметры, которые передаются в эти методы.

  1. path — путь до файла.
  2. options — объект или строка. Включает в себя следующие параметры:
    1. encoding — кодировка. Значение по умолчанию — null.
    2. flag — флаг файловой системы. Флаг задаёт режим работы с файлом. Например, можно создать файл, если его нет, а также открыть файл только для чтения или для чтения и записи. Значение по умолчанию — ‘r’ — открыть файл для чтения с выбросом исключения, если файла нет. Полный список флагов — по ссылке в официальной документации Node.js.
    3. signal — метод специального класса AbortSignal. Позволяет прерывать чтение файла. Используется только в асинхронной функции чтения.
  1. callback — функция, которая вызывается после завершения асинхронной функцией процесса по чтению файла. Принимает в себя два аргумента:
    1. err — объект ошибки, если она случилась.
    2. data — данные, полученные при чтении файла. Обычно представляются строкой или специальным классом Buffer.

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

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

Асинхронное чтение файла

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

fs.readFile('./access.log', (err, data) => console.log(data));

 

После запуска такого скрипта в терминале появится следующее:

<Buffer 31 32 37 2e 30 2e 30 2e 31 20 2d 20 2d 20 5b 33 30 2f 4a 75 6e 2f 32 30 31 36 3a 31 34 3a 31 30 3a 31 35 20 2d 30 34 30 30 5d 20 22 47 45 54 20 2f 65... 43 more bytes>

 

Это и есть данные, представленные классом Buffer. Заметим, что содержимое каждого байта представлено числом в шестнадцатеричной системе счисления. Подробнее об этой системе — здесь.

Преобразовываются эти двоичные данные в нормальный текст двумя способами.

Первый способ — вызвать метод toString у полученного объекта, который представлен классом Buffer, и указать кодировки:

fs.readFile('./access.log', (err, data) => console.log(data.toString('utf8')));

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

127.0.0.1 - - [30/Jan/2021:11:10:15 -0300] "GET /sitemap.xml HTTP/1.1" 200 0 "-" "curl/7.47.0"

 

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

fs.readFile('./access.log','utf8', (err, data) => console.log(data));

 

Результат в терминале будет аналогичен прошлому.

Синхронное чтение файла

В общем случае предпочтительно использовать асинхронное чтение файла, так как оно не блокирует поток программы. Однако если по какой-то причине программисту понадобится использовать синхронное чтение, то модуль fs предоставит такую возможность посредством метода readFileSync. Например, если мы пишем для себя какую-то небольшую консольную утилиту для работы с файлами, то можно воспользоваться простотой метода синхронного чтения.

const data = fs.readFileSync('./access.log'); console.log(data);

 

При запуске такого кода в терминале появится уже знакомый нам формат данных, представленный классом Buffer. Аналогично прошлому примеру, мы можем использовать кодировку, название которой передаём в метод readFileSync.

const data = fs.readFileSync('./access.log', 'utf8');

 

Мы научились считывать данные из файла логов, теперь запишем туда дополнительные логи.

Запись файла

Допустим, наш сервер принял ещё два запроса и ему требуется записать их в файл логов. Один запрос будет на запись данных:

127.0.0.1 - - [30/Jan/2021:11:11:20 -0300] "POST /foo HTTP/1.1" 200 0 "-" "curl/7.47.0"

И ещё один — на получение:

127.0.0.1 - - [30/Jan/2021:11:11:25 -0300] "GET /boo HTTP/1.1" 404 0 "-" "curl/7.47.0"

 

Как и в случае чтения файлов, модуль fs предоставляет для записи асинхронный и синхронный инструменты.

  1. writeFile(file, data[, options], callback) — асинхронная запись файла. По завершении записи вызывается переданный в метод коллбэк, где будет доступен объект ошибки, если она произошла в процессе записи файла.
  2. writeFileSync(file, data[, options]) — синхронная запись файла. Возвращает undefined.

Параметры, передающиеся в эти методы:

  1. file — имя файла, которое также включает и путь.
  2. data — данные для записи. Обычно представляются в формате строки, Buffer, объекта и специальных типов TypedArray и DataView. Прочитать об этих типах можно здесь и здесь.
  3. Options — объект или строка. Как правило, включает в себя следующие параметры:
    1. encoding — кодировка. Значение по умолчанию — 'utf8'.
    2. mode — этот параметр позволяет устанавливать специальные свойства файла, например, только для чтения. Значение по умолчанию — ‘0o666’ — файл доступен для чтения и записи любым пользователям.
    3. flag — флаг файловой системы. Разные флаги используются, чтобы создать файл, которого ещё нет, открыть файл только для чтения или для чтения и записи. Значение по умолчанию — ‘w’ — открыть файл для записи. Если файла нет, то он создастся, а, наоборот — перезапишется.
    4. signal — метод специального класса AbortSignal. Позволяет прерывать запись файла. Используется только в асинхронной функции записи.
  4. callback — функция, которая вызывается после завершения асинхронной функцией процесса записи файла. Принимает в себя только один аргумент:
    1. err — объект ошибки, если она случилась в процессе записи файла.

Протестируем эти методы на практике.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2021-12-15 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: