Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке buffer в Node.js?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Buffer** в Node.js - це масив байтів фіксованого розміру, що зберігається поза купою V8, для роботи з бінарними даними з файлів, потоків і мережевих операцій. ```js const buf = Buffer.from('Hello', 'utf8'); console.log(buf); // <Buffer 48 65 6c 6c 6f> console.log(buf.toString()); // "Hello" ``` **Ключове:** Buffer зберігає байти поза збирачем сміття V8, тому підходить для бінарного I/O, а не для звичайного тексту.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Buffer** - це масив байтів фіксованого розміру в Node.js, що зберігається поза купою V8 і використовується для роботи з бінарними даними: файлами, потоками, мережевими пакетами. ## Теорія ### TL;DR - Buffer схожий на піднос з пронумерованими осередками: кожен тримає один байт (0-255), доступ прямий за індексом - Головна різниця: Buffer зберігає сирі байти, рядки JavaScript кодують текст як UTF-16 (мінімум 2 байти на символ) - `Buffer.alloc(n)` дає n нульових байтів; `Buffer.allocUnsafe(n)` пропускає обнулення для швидкості, але може розкрити вміст старої пам'яті - Buffer для нетекстових даних (зображення, crypto-ключі, TCP-пакети); рядки для читабельного тексту - Зріз Buffer не копіює пам'ять: він повертає посилання на ті самі байти ### Швидкий приклад ```js // Створюємо, пишемо байти, читаємо як рядок const buf = Buffer.alloc(5); // 5 нульових осередків buf[0] = 0x48; // 'H' buf[1] = 0x65; // 'e' buf[2] = 0x6c; // 'l' buf[3] = 0x6c; // 'l' buf[4] = 0x6f; // 'o' console.log(buf.toString('utf8')); // "Hello" console.log(buf); // <Buffer 48 65 6c 6c 6f> // slice повертає посилання, не копію const view = buf.slice(0, 3); console.log(view.toString()); // "Hel" ``` `buf[0] = 0x48` записує байт для 'H' безпосередньо в пам'ять. `toString('utf8')` декодує ці байти назад у текст. Зріз ділить пам'ять з оригінальним `buf`, нового виділення не відбувається. ### Головна відмінність від рядків Рядки JavaScript - це UTF-16: кожен символ займає мінімум 2 байти і живе всередині купи V8 під управлінням збирача сміття. Buffer зберігає 8-бітні цілі числа (0-255) у пам'яті, виділеній через libuv, поза купою V8. Без тиску на GC, без накладних витрат кодування. Коли читаєш JPEG з диска, потрібні байти, не символи. Саме тут Buffer на своєму місці. ### Коли використовувати - Файловий I/O з бінарними форматами (зображення, PDF, zip): Buffer - Файловий I/O з текстом (JSON, CSV, логи): рядок - TCP/UDP сокети, сирі мережеві фрейми: Buffer - Тіло HTTP-відповіді, яке вже є текстом: рядок - Криптографічні операції (ключі, хеші, шифротекст): завжди Buffer, рядки можуть пошкодити байти через кодування - Стримінг великих файлів: обробляй чанками Buffer, декодуй у рядок тільки перед відображенням - `Buffer.allocUnsafe` підходить, якщо одразу перезаписуєш кожен байт; інакше використовуй `Buffer.alloc` ### Як це працює всередині Node.js виділяє пам'ять для Buffer через пули сирої C-пам'яті libuv, а не через V8. Збирач сміття не чіпає цю пам'ять під час звичайного виконання, що дозволяє уникнути пауз GC, коли тримаєш гігабайти відео або аудіо. V8 представляє Buffer як об'єкти, схожі на `Uint8Array`, з методами, підтриманими C++. Наприклад, `buf.write()` викликає `uv_buf_init` під капотом. Починаючи з Node.js 4, `Buffer` - це підклас `Uint8Array`, тому методи TypedArray працюють на ньому напряму. Зріз (slice) - це zero-copy: `buf.slice(0, 5)` повертає зміщення покажчика в той самий блок пам'яті, без нового виділення. Швидко, але зміни в зрізі зачіпають оригінал. ### Типові помилки **Помилка 1: використання `Buffer.allocUnsafe` без перезапису всіх байтів** ```js const buf = Buffer.allocUnsafe(10); buf.write('hi'); // Байти на позиціях 2-9 можуть містити стару пам'ять процесу console.log(buf); // <Buffer 68 69 XX XX XX XX XX XX XX XX> ``` `allocUnsafe` пропускає обнулення заради продуктивності. Якщо відправиш цей Buffer по мережі або запишеш на диск, незаписані байти розкривають те, що було в тій пам'яті раніше. На практиці це найпоширніша причина тихого пошкодження даних у бінарних пайплайнах. Я бачив як команди витрачали пів дня на дебаг пошкоджених зображень, поки не відстежили проблему до `allocUnsafe` з незаписаними хвостовими байтами. Рішення: використовуй `Buffer.alloc(10)` або одразу після `allocUnsafe` викликай `buf.fill(0)`. **Помилка 2: припущення що `slice` копіює дані** ```js const big = Buffer.alloc(1_000_000); const small = big.slice(0, 10); small[0] = 0xff; console.log(big[0]); // 255 - ти мутував big теж ``` Зріз ділить пам'ять з оригіналом. Якщо потрібна незалежна копія, використовуй `Buffer.from(small)`. **Помилка 3: зріз посеред emoji при роботі з Unicode** ```js const buf = Buffer.from('😊👍', 'utf8'); console.log(buf.length); // 8 байтів (4 на emoji в UTF-8) const broken = buf.slice(0, 3); // ріже першу emoji посередині console.log(broken.toString('utf8')); // пошкоджений вивід const correct = buf.toString('utf8', 0, 4); // повна перша emoji console.log(correct); // "😊" ``` Buffer працює на рівні байтів, не символів. Багатобайтові Unicode-символи пошкоджуються, якщо зміщення не збігається з межею символу. **Помилка 4: забути передати кодування `'hex'`** ```js // Неправильно: рядок hex читається як UTF-8 текст Buffer.from('48656c6c6f'); // Правильно Buffer.from('48656c6c6f', 'hex'); // <Buffer 48 65 6c 6c 6f> = "Hello" ``` Кодування за замовчуванням - `'utf8'`. Завжди передавай другий аргумент при роботі з hex або base64. **Помилка 5: сприймати Buffer як незмінний, як рядок** ```js const buf = Buffer.from('test'); buf[0] = 0x48; // змінює 't' на 'H' console.log(buf.toString()); // "Hest" ``` Buffer - це мутабельний масив байтів. Якщо потрібна незмінна копія, створи її через `Buffer.from(buf)`. ### Де зустрічається в реальному коді - Express + Multer: завантажені файли приходять як `req.file.buffer`, хешуєш або скануєш перед записом на диск - Бібліотека Sharp: `sharp(buffer).resize().toBuffer()` обробляє JPEG і PNG повністю в пам'яті - Модуль crypto: `crypto.createCipheriv(algorithm, keyBuffer, ivBuffer)` для AES-шифрування - `fs.createReadStream` видає Buffer-чанки; ти контролюєш коли декодувати у рядок - `net.Socket.write(buffer)` для сирих TCP-фреймів, шлях без копіювання ### Питання для поглиблення **Q:** Яка різниця між `Buffer.alloc` і `Buffer.allocUnsafe`? **A:** `alloc` заповнює пам'ять нулями перед поверненням, безпечно але трохи повільніше. `allocUnsafe` пропускає обнулення для швидкості, але байти містять те, що було в тій пам'яті раніше. Використовуй `allocUnsafe` тільки якщо одразу перезаписуєш кожну позицію. **Q:** Чому Buffer виділяється поза купою V8? **A:** Щоб уникнути пауз збирача сміття. Якщо тримати 1 ГБ відеопотоку всередині купи V8, GC мусить сканувати і відстежувати його. Пам'ять під управлінням libuv невидима для GC, тому великі бінарні дані не викликають пауз. **Q:** `Buffer` проти `Uint8Array` в Node.js 20. Що обрати? **A:** `Buffer` - підклас `Uint8Array`, вони взаємосумісні. Для I/O API Node.js (потоки, crypto, http) Buffer залишається природним вибором, бо API повертає Buffer. Для нового утилітарного коду без I/O підходить `Uint8Array`, він більш портативний для браузерного середовища. При стабільному I/O на файлах від 1 ГБ Buffer backed by libuv уникає GC-пауз, які може викликати `Uint8Array`. **Q:** Як працює `Buffer.concat` і чи є дешевший варіант? **A:** `Buffer.concat([buf1, buf2])` виділяє новий Buffer і копіює всі дані туди, O(n) від загального розміру. Zero-copy concat не існує. Якщо потрібно лише читати через кілька буферів, тримай їх у масиві і відстежуй зміщення вручну. **Q:** Що станеться, якщо передати великий Buffer у writable stream без обробки backpressure? **A:** Внутрішній буфер потоку заповниться. Якщо споживач повільний, дані накопичуватимуться в пам'яті доки процес не вичерпає її. Рішення: використовувати `stream.pipeline()` або перевіряти повертане значення `write()` і ставити джерело на паузу, коли воно повертає `false`. ## Приклади ### Базовий: рядок у Buffer і назад ```js const str = 'Hello'; const buf = Buffer.from(str, 'utf8'); console.log(buf); // <Buffer 48 65 6c 6c 6f> console.log(buf.length); // 5 console.log(buf[0]); // 72 (десятковий для 0x48) console.log(buf.toString('utf8')); // "Hello" console.log(buf.toString('hex')); // "48656c6c6f" console.log(buf.toString('base64')); // "SGVsbG8=" ``` `Buffer.from(str, 'utf8')` кодує кожен символ у відповідне байтове представлення UTF-8. `toString` декодує назад. Для чистого ASCII як "Hello" кількість байтів збігається з кількістю символів. Додай не-ASCII символ і кількість байтів зросте. ### Практичний: обробник завантаження файлу з перевіркою хешу ```js const crypto = require('crypto'); const fs = require('fs'); // Multer зберігає завантажений файл як req.file.buffer app.post('/upload', (req, res) => { const fileBuffer = req.file.buffer; if (fileBuffer.length > 1_000_000) { return res.status(413).send('Файл занадто великий'); } const hash = crypto .createHash('sha256') .update(fileBuffer) .digest('hex'); fs.writeFileSync(`uploads/${hash}.jpg`, fileBuffer); res.send({ saved: hash }); }); ``` Байти файлу залишаються як Buffer від отримання по HTTP до запису на диск. Жодного проміжного перетворення в рядок, жодних проблем з кодуванням. Хеш обчислюється безпосередньо на бінарних даних - саме це потрібно для перевірки цілісності. ### Поглиблений: крайній випадок зрізу при багатобайтовому Unicode ```js const emoji = '😊👍'; const buf = Buffer.from(emoji, 'utf8'); console.log(buf.length); // 8 байтів (кожна emoji займає 4 байти в UTF-8) console.log(emoji.length); // 2 (JS рахує кодові одиниці, не байти) // Неправильно: байтове зміщення 3 всередині першої emoji const broken = buf.slice(0, 3); console.log(broken.toString('utf8')); // пошкоджений вивід // Правильно: повна межа 4 байти console.log(buf.toString('utf8', 0, 4)); // "😊" console.log(buf.toString('utf8', 4, 8)); // "👍" ``` Це підловлює розробників, які припускають що кількість байтів дорівнює кількості символів. Для багатобайтових символів завжди обчислюй зміщення через `Buffer.byteLength(str, 'utf8')` або спочатку працюй на рівні рядка, потім конвертуй у Buffer.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.