DanLevy.net

Тест: Мастерство ввода‑вывода NodeJS

Проверьте свои знания о файлах, потоках и буферах

Готовы погрузиться в мир NodeJS I/O? 🌊

Этот тест проверит, насколько вы понимаете операции ввода‑вывода Node, от базовых действий с файловой системой до продвинутых концепций потоков. Мы разберём буферы, кодировки и лучшие практики эффективной обработки данных.

Посмотрим, насколько вы знакомы со своими потоками и буферами! 🚀

Что делает этот код?

const buf = Buffer.alloc(5);
console.log(buf);

Buffer.alloc(size) создаёт новый Buffer указанного размера, заполненный нулями. Вывод будет: <Buffer 00 00 00 00 00>

Если хотите создать Buffer со случайными данными, используйте Buffer.allocUnsafe(5).

Узнайте больше о выделении Buffer

Что будет выведено?

const buf = Buffer.from([65]);
console.log(buf.toString());

Числа в массиве представляют ASCII‑коды:

  • 65: ‘A’

toString() преобразует эти байты в их строковое представление, используя кодировку UTF-8 по умолчанию.

Узнать больше о кодировке Buffer

Какой порядок вывода?

import fs from 'fs';
fs.readFile('test.txt', 'utf8', (err, data) => {
console.log(data);
});
console.log('Done');

Поскольку readFile асинхронен, код продолжает выполняться, пока файл читается. Поэтому “Done” будет выведено до содержимого файла.

Чтобы дождаться чтения файла, можно использовать версию на основе Promise:

import { promises as fs } from 'fs';
async function read() {
const data = await fs.readFile('test.txt', 'utf8');
console.log(data);
console.log('Done');
}

Что возвращает fs.readFileSync() по умолчанию?

import fs from 'fs';
const content = fs.readFileSync('test.txt');

fs.readFileSync() возвращает Buffer по умолчанию, если не указана кодировка. Если нужна строка, нужно либо:

  1. Указать кодировку: fs.readFileSync('test.txt', 'utf8')
  2. Преобразовать Buffer: content.toString()

Узнать больше о fs.readFileSync в документации Node.js

Какой набор событий обычно используется с Readable‑потоками?

Readable потоки генерируют несколько важных событий:

  • ‘data’: Когда данные доступны для чтения
  • ‘end’: Когда больше нет данных для чтения
  • ‘error’: Когда происходит ошибка
  • ‘close’: Когда поток и подлежащий ресурс закрыты
const readable = fs.createReadStream('file.txt');
readable.on('data', chunk => console.log(chunk));
readable.on('end', () => console.log('Done!'));

Узнать больше о событиях Stream

Что делает этот код?

import fs from 'fs';
const readable = fs.createReadStream('source.txt');
const writable = fs.createWriteStream('dest.txt');
readable.pipe(writable);

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

Это экономит память при работе с большими файлами по сравнению с fs.readFile() и последующим fs.writeFile().

Узнать больше о pipe()

Что делает опция recursive?

import fs from 'fs';
fs.mkdirSync('./a/b/c', { recursive: true });

recursive: true опция создаёт родительские каталоги, если их нет. Без этой опции попытка создать ’./a/b/c’ вызовет ошибку, если ’./a’ или ’./a/b’ не существуют.

Это аналогично shell‑команде mkdir -p.

Узнать больше о mkdir

Что будет выведено?

import { Transform } from 'stream';
const upperCase = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase());
}
});
process.stdin
.pipe(upperCase)
.pipe(process.stdout);
// Input: "hello world"

Трансформирующие потоки изменяют данные по мере их прохождения. Здесь каждый кусок данных:

  1. Преобразуется в строку
  2. Переводится в верхний регистр
  3. Передаётся в stdout

Это создаёт конвейер, который переводит весь ввод в верхний регистр.

Узнать больше о трансформирующих потоках

Сколько раз fs.watch() гарантировано сработает при изменении файла?

import fs from 'fs';
fs.watch('test.txt', (eventType, filename) => {
console.log(`${filename} was changed`);
});
// Then modify test.txt once

fs.watch() не гарантирует срабатывание ровно один раз за логическое изменение файла. Часто он срабатывает несколько раз, потому что многие текстовые редакторы:

  1. Сохраняют в временный файл
  2. Переименовывают его в целевой файл

Для более надёжного отслеживания рассмотрите:

  • Пакет chokidar
  • Дебаунсинг колбэка
  • Использование fs.watchFile() (хотя он менее эффективен)

Узнать больше о fs.watch()

Какой будет вывод?

const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from('Hello');
console.log(buf1 === buf2);

Буферы сравниваются по ссылке, а не по значению. Хотя они содержат одинаковые данные, они являются разными объектами.

Чтобы сравнить содержимое буферов, используйте:

buf1.equals(buf2) // true
// or
Buffer.compare(buf1, buf2) === 0 // true

Узнать больше о сравнении буферов

Какова основная цель обратного давления в потоках?

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

Пример ручного обратного давления:

readable.on('data', (chunk) => {
const canContinue = writable.write(chunk);
if (!canContinue) {
readable.pause();
writable.once('drain', () => readable.resume());
}
});

pipe() обрабатывает это автоматически!

Узнать больше об обратном давлении

Что делает этот код?

import fs from 'fs';
fs.symlinkSync('target.txt', 'link.txt');

symlinkSync создаёт символическую ссылку (как ярлык) на целевой файл.

Ключевые отличия от жёстких ссылок:

  • Может ссылаться на каталоги
  • Может пересекать файловые системы
  • Прерывается, если цель удалена

Чтобы вместо этого создать жёсткую ссылку:

fs.linkSync('target.txt', 'hardlink.txt');

Узнать больше о символических ссылках

В каких режимах могут работать потоки Node.js?

Потоки могут работать в:

  1. Двоичный режим (по умолчанию): для буферов и строк
  2. Объектный режим: для любого значения JavaScript

Пример объектного режима:

import { Transform } from 'stream';
const objectStream = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
callback(null, { value: chunk });
}
});

Узнать больше о режимах потоков

Какого типа параметр fd в этом колбэке?

import fs from 'fs';
fs.open('test.txt', 'r', (err, fd) => {
console.log(typeof fd);
});

Файловые дескрипторы — это числа, которые уникально идентифицируют открытые файлы в операционной системе.

Первые три файловых дескриптора зарезервированы:

  • 0: stdin
  • 1: stdout
  • 2: stderr

Всегда помните закрывать файловые дескрипторы:

fs.close(fd, (err) => {
if (err) throw err;
});

Узнать больше о файловых дескрипторах

Сколько байт займет эта строка в UTF-8?

const str = "Hello 🌍";
const buf = Buffer.from(str);
console.log(buf.length);

В UTF-8:

  • ASCII‑символы (например ‘Hello ’) занимают 1 байт каждый
  • Эмодзи земли 🌍 занимает 4 байта

Итого: 5 (Hello) + 1 (пробел) + 4 (🌍) = 10 байт

Чтобы увидеть байты:

console.log(buf); // <Buffer 48 65 6c 6c 6f 20 f0 9f 8c 8d>

Узнать больше о кодировке UTF-8

Надеюсь, вам понравилось проверять свои знания по NodeJS IO! Хотите продолжить? Загляните в мою Коллекцию викторин для новых задач!