Одна из самых коварных ловушек в Node.js — смешивание
Buffer и Uint8Array при парсинге бинарных протоколов. На первый взгляд они взаимозаменяемы: Buffer наследует от Uint8Array. Но под капотом — разные контракты, и "тихая" конвертация данных может убить zero-copy или вызвать TypeError.Проблема: slice vs subarray
Допустим, ты парсишь TCP-поток с zero-copy, работая с подмассивами:
// Ошибка: buffer.slice() копирует данные в Node <20
function parsePacket(raw: Buffer): number {
const header = raw.slice(0, 4);
return header.readUInt32BE(); // работает, но копия
}
// Правильно: subarray() делает zero-copy view
function parsePacket(raw: Buffer): number {
const header = raw.subarray(0, 4); // Uint8Array
// TypeError: header.readUInt32BE is not a function
}
Ключевые грабли
*
Buffer.prototype.slice() до Node 20 копирует данные, убивая zero-copy.*
Buffer.prototype.subarray() возвращает Uint8Array, а не Buffer. Методы .readUInt* недоступны.* Обратная конвертация (передача
Uint8Array в Buffer API) — тихая, но с сюрпризом: методы Buffer не работают.Правильный подход: DataView
Для кросс-платформенного zero-copy парсинга используй
DataView поверх ArrayBuffer:class PacketParser {
private view: DataView;
constructor(private buffer: ArrayBuffer | Buffer) {
this.view = new DataView(
buffer instanceof Buffer ? buffer.buffer : buffer
);
}
readUint32(offset: number): number {
return this.view.getUint32(offset, false); // big-endian
}
readSlice(offset: number, length: number): ArrayBuffer {
return this.buffer.slice(offset, offset + length);
}
}
// Zero-copy: subarray, затем DataView
const raw = Buffer.from([0, 0, 0, 42]);
const firstPacket = raw.subarray(0, 4); // Uint8Array
const parser = new PacketParser(firstPacket.buffer);
console.log(parser.readUint32(0)); // 42Практические советы
* Не используй
Buffer.read* при zero-copy — они не работают с Uint8Array.* Для кросс-платформенного парсинга (браузер + Node) — только
DataView.*
Buffer.concat возвращает Buffer и копирует данные — явная конвертация.* Если нужен гибридный парсер, оберни все в
new Uint8Array(bufferOrView) и используй TextDecoder или DataView.Предупреждение
Неявное смешивание типов — главный источник багов производительности и логики. Zero-copy теряется, как только ты вызываешь
.slice() или передаешь Buffer в API браузера (например, WebSocket.send).Вывод: Для надежного zero-copy парсинга бинарных протоколов используй
DataView поверх subarray — это единственный способ избежать тихих конвертаций и сохранить производительность в production.