У большинства людей есть способности и возможности, о которых они не знают, и можно делать то, о чем они и не мечтали. - Дейл Карнеги
Детская обувь, перенесенная с фронтенда на Node.js, будет относительно незнакома с этой частью контента, потому что некоторые простые строковые операции во фронтенде уже отвечают базовым потребностям бизнеса, а иногда Buffer и Stream могут казаться загадочными. Вернемся к серверной части. Если вы не хотите быть обычным разработчиком Node.js, вам следует углубиться в Buffer, чтобы раскрыть эту тайну, и в то же время это также улучшит ваше понимание Node.js, чтобы более высокий уровень.
об авторе: Май Джун, разработчик Nodejs, молодой человек после 90-х, который любит технологии и любит делиться, публичный аккаунт «Стек технологий Nodejs», проект с открытым исходным кодом Github.www.nodejs.red
Буфер первого знакомства
До появления TypedArray в языке JavaScript не было механизма для чтения или управления потоками двоичных данных. Класс Buffer был представлен как часть Node.js API для взаимодействия с потоками октетов в потоках TCP, операциях файловой системы и других контекстах. Это описание с официального сайта Node.js, довольно туманное и сложное для понимания, если резюмировать одним предложением.Node.js можно использовать для обработки двоичных потоковых данных или взаимодействия с ними..
Буфер используется для чтения или манипулирования потоками двоичных данных. Он не требует require при использовании в составе Node.js API. Он используется для работы сетевых протоколов, баз данных, операций ввода-вывода изображений и файлов и других сценариев, требующих большого объема памяти. количество двоичных данных. Размер буфера был определен во время создания и не может быть изменен.При распределении памяти этот буфер предоставляется уровнем C++ вместо V8.Это будет объяснено позже.
Не уверены здесь, если вы думаете, что это легко? Но некоторые из упомянутых выше ключевых слов二进制
,流(Stream)
,缓冲区(Buffer)
, что это? Давайте попробуем сделать несколько простых вступлений.
Что такое бинарные данные?
Когда дело доходит до двоичного кода, наш мозг может думать о такой кодовой команде, как 010101, как показано на следующем рисунке:
Как показано на рисунке выше, двоичные данные — это данные, представленные двумя числами, 0 и 1. Чтобы сохранить или отобразить некоторые данные, компьютер должен преобразовать данные в двоичные данные для их представления. Например, если я хочу сохранить число 66, компьютер сначала преобразует число 66 в двоичное представление 01000010. Я помню, что первый контакт с этим был на курсе языка C в колледже. Формула преобразования выглядит следующим образом:
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
Выше приведен пример с числами, мы знаем, что числа — это только один из типов данных, есть и другие строки, изображения, файлы и т. д. Например, мы работаем с английской буквой M и передаем ее в JavaScript.'M'.charCodeAt()
После получения соответствующего ASCII-кода (с помощью вышеуказанных шагов) он будет преобразован в двоичное представление.
Что такое поток?
Поток, английский Поток — это абстракция устройств ввода и вывода, устройствами здесь могут быть файлы, сети, память и т. д.
Поток направленный.Когда программа читает данные из источника данных, она открывает входной поток.Источником данных здесь может быть файл или сеть и т. д. Например, мы читаем данные из файла .txt. Наоборот, когда нашей программе нужно записать данные в указанный источник данных (файл, сеть и т. д.), открывается выходной поток. Когда есть какие-то операции с большими файлами, нам нужен Stream для передачи данных по крупицам, как конвейер.
Например
Теперь у нас есть большой кувшин с водой, которую нужно вылить на овощное поле.Если всю воду в кувшине вылить сразу на овощное поле, то сколько усилий потребуется сначала (сила здесь подобна производительности оборудования в компьютер), чтобы переместить его. Если мы понемногу принесем водопроводную трубу, чтобы вода текла на наше огородное поле, это можно сделать без особых усилий в настоящее время.
Благодаря приведенному выше объяснению вы лучше понимаете, что такое Stream? Итак, какова связь между Stream и Buffer? Глядя на следующее введение, вы также много знаете о самом Stream.Добро пожаловать в общедоступный аккаунт «Nodejs Technology Stack», который будет представлен отдельно позже.
Что такое буфер?
В приведенном выше объяснении Stream мы увидели, что данные передаются от одного конца к другому, так как же они передаются?
Часто данные перемещаются для того, чтобы обработать или прочитать их и принять решения на их основе. Со временем каждый процесс будет иметь минимальный или максимальный объем данных. Если данные поступают быстрее, чем процесс может их использовать, то небольшое количество данных, которые поступают раньше, будет находиться в зоне ожидания, ожидая обработки. И наоборот, если данные поступают медленнее, чем потребляет процесс, данные, которые поступают раньше, должны ждать поступления определенного количества данных, прежде чем их можно будет обработать.
Область ожидания здесь относится к буферу (Buffer), который представляет собой небольшую физическую единицу в компьютере, обычно расположенную в оперативной памяти компьютера. Эти концепции могут быть трудными для понимания, не беспокойтесь о дальнейшей иллюстрации с помощью примера ниже.
Пример посадки на автобус на остановке
Возьмем для примера посадку автобуса на автовокзале.Обычно автобус ходит каждые десятки минут.Даже если пассажиры заполнены до этого времени,автобус не уедет заранее.Пассажирам,прибывшим рано,нужно идти на станция первая. подожди. Предполагая, что прибывающих пассажиров слишком много, некоторым из опоздавших приходится ждать следующего автобуса на остановке.
В приведенном выше примере остановка автобуса в зоне ожидания соответствует буферу (Buffer) в нашем Node.js, кроме того, скорость прибытия пассажиров нам неподвластна, мы можем контролировать только время отправления автобуса. , что соответствует В нашей программе мы не можем контролировать, когда прибывает поток данных, что мы можем сделать, так это решить, когда отправлять данные.
Основное использование буфера
После понимания некоторых концепций Buffer, давайте взглянем на некоторые основные способы использования Buffer. Мы не будем перечислять здесь все виды использования API, а только некоторые часто используемые. Для получения более подробной информации см.Китайская сеть Node.js.
Создать буфер
В версиях Node.js до 6.0.0 экземпляры Buffer создавались с помощью конструктора Buffer, который распределял возвращаемый Buffer по-разному в зависимости от предоставленных аргументов.new Buffer()
.
Теперь его можно создать тремя способами: Buffer.from(), Buffer.alloc() и Buffer.allocUnsafe().
Buffer.from()
const b1 = Buffer.from('10');
const b2 = Buffer.from('10', 'utf8');
const b3 = Buffer.from([10]);
const b4 = Buffer.from(b3);
console.log(b1, b2, b3, b4); // <Buffer 31 30> <Buffer 31 30> <Buffer 0a> <Buffer 0a>
Buffer.alloc
Возвращает инициализированный буфер, который гарантированно никогда не содержит старых данных.
const bAlloc1 = Buffer.alloc(10); // 创建一个大小为 10 个字节的缓冲区
console.log(bAlloc1); // <Buffer 00 00 00 00 00 00 00 00 00 00>
Buffer.allocUnsafe
Создает новый неинициализированный буфер размера size байтов.Поскольку буфер неинициализирован, выделенный фрагмент памяти может содержать конфиденциальные старые данные. Если содержимое буфера читаемо, возможна утечка его старых данных, что небезопасно и должно использоваться с осторожностью.
const bAllocUnsafe1 = Buffer.allocUnsafe(10);
console.log(bAllocUnsafe1); // <Buffer 49 ae c9 cd 49 1d 00 00 11 4f>
Буферная кодировка символов
Используя кодировку символов, можно реализовать взаимное преобразование между экземплярами Buffer и строками JavaScript.В настоящее время поддерживаются следующие кодировки символов:
- 'ascii' — только для 7-битных данных ASCII. Это кодирование быстрое и удалит старшие биты, если установлено.
- 'utf8' - многобайтовые кодированные символы Unicode. Многие веб-страницы и другие форматы документов используют UTF-8.
- 'utf16le' — 2 или 4 байта, символы Unicode с прямым порядком байтов. Поддерживаются суррогатные пары (от U+10000 до U+10FFFF).
- ucs2 — псевдоним для utf16le.
- 'base64' - кодировка Base64. Эта кодировка также правильно принимает «безопасные буквы URL и имени файла», как указано в RFC 4648, раздел 5, при создании буфера из строки.
- 'latin1' — метод кодирования буфера в однобайтовую закодированную строку (определено IANA в RFC 1345, стр. 63, как дополнительные блоки Latin-1 и управляющие коды C0/C1).
- 'binary' - Псевдоним для 'latin1'.
- 'hex' - кодировать каждый байт как два шестнадцатеричных символа.
const buf = Buffer.from('hello world', 'ascii');
console.log(buf.toString('hex')); // 68656c6c6f20776f726c64
Преобразование типа строки и буфера
Строка в буфер
Я думаю, что это не незнакомо.Это реализовано с помощью Buffer.form(), описанного выше.Если кодировка не передается, она будет преобразована и сохранена в формате UTF-8 по умолчанию.
const buf = Buffer.from('Node.js 技术栈', 'UTF-8');
console.log(buf); // <Buffer 4e 6f 64 65 2e 6a 73 20 e6 8a 80 e6 9c af e6 a0 88>
console.log(buf.length); // 17
Буфер в строку
Преобразование буфера в строку также очень просто. Используйте метод toString([encoding], [start], [end]). Кодировка по умолчанию по-прежнему UTF-8. Если start и end не переданы, все преобразования могут быть достигнуто. Возможна частичная конвертация (будьте осторожны)
const buf = Buffer.from('Node.js 技术栈', 'UTF-8');
console.log(buf); // <Buffer 4e 6f 64 65 2e 6a 73 20 e6 8a 80 e6 9c af e6 a0 88>
console.log(buf.length); // 17
console.log(buf.toString('UTF-8', 0, 9)); // Node.js �
Запустив представление, вы увидите, что приведенный выше выводNode.js �
Появляются искаженные символы, почему?
Почему в процессе преобразования появляются искаженные символы?
Во-первых, метод кодировки по умолчанию, используемый в приведенном выше примере, — UTF-8, проблема в том, что китайский занимает 3 байта в UTF-8.技
Байт, соответствующий этому слову в buf, равен8a 80 e6
И задаем диапазон от 0 до 9, поэтому выводим только8a
, на этот раз символы будут усечены и искажены.
Давайте изменим диапазон перехвата примера ниже:
const buf = Buffer.from('Node.js 技术栈', 'UTF-8');
console.log(buf); // <Buffer 4e 6f 64 65 2e 6a 73 20 e6 8a 80 e6 9c af e6 a0 88>
console.log(buf.length); // 17
console.log(buf.toString('UTF-8', 0, 11)); // Node.js 技
Вы можете видеть, что вывод нормальный
Механизм буферной памяти
существуетУправление памятью и механизм сборки мусора V8 в NodejsВ этом разделе в основном объясняется, что V8 в основном используется для управления сборкой мусора Node.js, но не упоминается, как восстанавливаются данные типа Buffer.Давайте разберемся с механизмом восстановления памяти Buffer.
Поскольку Buffer должен иметь дело с большим объемом двоичных данных, если вы используете немного для применения к системе, это вызовет частые обращения к памяти в системе, поэтому память, занимаемая BufferБольше не выделяется V8, но в Node.jsЗавершить приложение на уровне C++,существуетРаспределение памяти в JavaScript. Поэтому эту часть памяти мы называемпамять вне кучи.
Уведомление: Исходный код buffer.js, используемый ниже, представляет собой версию Node.js v10.x, адрес:GitHub.com/node будет /node…
Принцип выделения буферной памяти
Node.js использует механизм slab дляДо подачи заявки, после назначения, представляет собой динамический механизм управления.
Использование Buffer.alloc(size) для передачи указанного размера будет применяться к области памяти фиксированного размера.Плита имеет следующие три состояния:
- full: полностью выделенное состояние
- частично: статус частичного распределения
- пусто: состояние не назначено
Ограничение 8 КБ
Node.js использует 8 КБ в качестве границы, чтобы различать маленькие объекты и большие объекты.buffer.jsВы можете увидеть следующий код в
Buffer.poolSize = 8 * 1024; // 102 行,Node.js 版本为 v10.x
существуетБуфер первого знакомстваупоминается в разделеBuffer 在创建时大小已经被确定且是无法调整的
Теперь должно быть ясно.
Распределение объектов буфера
В следующем примере кода прямой вызов createPool() при загрузке эквивалентен прямой инициализации пространства памяти размером 8 КБ, что также становится более эффективным при первом выделении памяти. Кроме того, новая переменная инициализируется одновременно с инициализациейpoolOffset = 0Эта переменная будет отслеживать, сколько байтов было использовано.
Buffer.poolSize = 8 * 1024;
var poolSize, poolOffset, allocPool;
... // 中间代码省略
function createPool() {
poolSize = Buffer.poolSize;
allocPool = createUnsafeArrayBuffer(poolSize);
poolOffset = 0;
}
createPool(); // 129 行
На данный момент только что построенная плита выглядит так:
Теперь попробуем выделить объект Buffer размером 2048, код такой:
Buffer.alloc(2 * 1024)
Теперь давайте посмотрим, как выглядит текущая slab память? Следующим образом:
Так как же выглядит процесс распределения? Давайте посмотрим на другой основной метод буфера.js allocate(size)
// https://github.com/nodejs/node/blob/v10.x/lib/buffer.js#L318
function allocate(size) {
if (size <= 0) {
return new FastBuffer();
}
// 当分配的空间小于 Buffer.poolSize 向右移位,这里得出来的结果为 4KB
if (size < (Buffer.poolSize >>> 1)) {
if (size > (poolSize - poolOffset))
createPool();
var b = new FastBuffer(allocPool, poolOffset, size);
poolOffset += size; // 已使用空间累加
alignPool(); // 8 字节内存对齐处理
return b;
} else { // C++ 层面申请
return createUnsafeBuffer(size);
}
}
Прочитав приведенный выше код, вы можете четко видеть, когда следует размещать небольшие объекты Buffer, а когда — большие объекты Buffer.
Сводка по распределению буферной памяти
Это содержание действительно сложно понять. Я прочитал несколько книг, связанных с Node.js. Раздел «Буфер» «Введение в Node.js» г-на Пу Линга все еще подробно объясняется. Я рекомендую всем прочитать его.
- 1 инициализируется при первой загрузке8 КБ памяти, исходный код buffer.js отражается
- По объему памяти приложение делится наНебольшой буферный объекта такжеБольшой буферный объект
- В случае небольшого буфера он будет продолжать судить о том, достаточно ли места на плите.
- Если места достаточно, чтобы использовать оставшееся пространство и обновить состояние распределения плит, смещение увеличится.
- Если места недостаточно, а места на плите недостаточно, для выделения будет создано новое пространство плиты.
- В случае большого буфера функция createUnsafeBuffer(size) будет использоваться напрямую.
- Независимо от того, является ли это небольшим объектом Buffer или большим объектом Buffer, выделение памяти выполняется на уровне C++, а управление памятью осуществляется на уровне JavaScript, который в конечном итоге может быть освобожден маркером сборки мусора V8.
Сценарии применения буфера
Ниже перечислены некоторые сценарии применения Buffer в реальном бизнесе, и вы можете добавить их в область комментариев!
операции ввода/вывода
Что касается ввода-вывода, это может быть файловый или сетевой ввод-вывод. Далее следует прочитать информацию из input.txt через поток и записать ее в файл output.txt. Если вы не понимаете взаимосвязь между потоком и буфером, оглядыватьсяБуфер первого знакомствараздел объяснил什么是 Stream?
,什么是 Buffer?
const fs = require('fs');
const inputStream = fs.createReadStream('input.txt'); // 创建可读流
const outputStream = fs.createWriteStream('output.txt'); // 创建可写流
inputStream.pipe(outputStream); // 管道读写
В Stream нам не нужно вручную создавать собственный буфер, в Node.jsбудет автоматически создан в потоке.
zlib.js
zlib.js — одна из основных библиотек Node.js, которая использует функцию буфера (Buffer) для работы с потоком двоичных данных и предоставляет функцию сжатия или распаковки. Справочный исходный кодисходный код zlib.js
Шифрование и дешифрование
Буфер встречается в некоторых алгоритмах шифрования и дешифрования. Например, второй ключ параметра crypto.createCipheriv имеет тип String или Buffer. Если это тип Buffer, используется содержимое, которое мы объяснили в этой статье. Ниже приведено простое шифрование. Например, сосредоточьтесь на использовании Buffer.alloc() для инициализации экземпляра (это описано выше), а затем используйте метод fill для его заполнения.Здесь мы сосредоточимся на использовании этого метода.
buf.fill(value[, offset[, end]][, encoding])
- значение: первый параметр — это содержимое, которое нужно заполнить
- offset: смещение, начальная позиция заполнения
- end: смещение до конца буфера заполнения
- кодировка: набор кодировок
Ниже приведена демонстрация симметричного шифрования Cipher.
const crypto = require('crypto');
const [key, iv, algorithm, encoding, cipherEncoding] = [
'a123456789', '', 'aes-128-ecb', 'utf8', 'base64'
];
const handleKey = key => {
const bytes = Buffer.alloc(16); // 初始化一个 Buffer 实例,每一项都用 00 填充
console.log(bytes); // <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
bytes.fill(key, 0, 10) // 填充
console.log(bytes); // <Buffer 61 31 32 33 34 35 36 37 38 39 00 00 00 00 00 00>
return bytes;
}
let cipher = crypto.createCipheriv(algorithm, handleKey(key), iv);
let crypted = cipher.update('Node.js 技术栈', encoding, cipherEncoding);
crypted += cipher.final(cipherEncoding);
console.log(crypted) // jE0ODwuKN6iaKFKqd3RF4xFZkOpasy8WfIDl8tRC5t0=
Buffer VS Cache
В чем разница между буфером и кэшем?
Буфер
Буфер используется для обработки двоичных потоковых данных и буферизации данных. Он является временным. Для потоковых данных буфер будет использоваться для временного хранения данных, и после буферизации до определенного размера они будут сохранены на жестком диске. Видеоплеер является классическим примером, иногда вы увидите значок буферизации, что означает, что набор буферов в это время не заполнен, когда данные поступают для заполнения буфера и обрабатываются, значок буферизации в это время исчезает и вы можете увидеть некоторые данные изображения.
Кэш
Кэш можно рассматривать как промежуточный слой, который может постоянно кэшировать горячие данные для ускорения доступа, например, мы запрашиваем данные с жесткого диска или других сторонних интерфейсов через Память, Redis и т. д. Цель кэширования — хранить данные в области кеша памяти, так что доступ к тому же ресурсу будет быстрее, а это тоже важный момент в оптимизации производительности.
Обсуждение от Zhihu, нажмитеmoreПроверить
Buffer VS String
Как насчет производительности String и Buffer в ходе стресс-теста?
const http = require('http');
let s = '';
for (let i=0; i<1024*10; i++) {
s+='a'
}
const str = s;
const bufStr = Buffer.from(s);
const server = http.createServer((req, res) => {
console.log(req.url);
if (req.url === '/buffer') {
res.end(bufStr);
} else if (req.url === '/string') {
res.end(str);
}
});
server.listen(3000);
Вышеприведенный пример я поместил в виртуальную машину для тестирования, вы также можете протестировать его на локальном компьютере и использовать инструмент тестирования AB.
тестовая строка
Посмотрите на следующие важные показатели параметров, а затем сравните их через буферную передачу
- Complete requests: 21815
- Requests per second: 363.58 [#/sec] (mean)
- Transfer rate: 3662.39 [Kbytes/sec] received
$ ab -c 200 -t 60 http://192.168.6.131:3000/string
тестовый буфер
Видно, что общее количество запросов, переданных через буфер, составляет 50 000, число запросов в секунду увеличено более чем в два раза, а количество передаваемых байтов в секунду составляет 9138,82 КБ.Из этих данных можно доказать, что метод преобразования данных в буфер заранее может повысить производительность почти вдвое.
- Complete requests: 50000
- Requests per second: 907.24 [#/sec] (mean)
- Transfer rate: 9138.82 [Kbytes/sec] received
$ ab -c 200 -t 60 http://192.168.6.131:3000/buffer
При передаче HTTP передаются двоичные данные. Строка, возвращаемая интерфейсом /string в приведенном выше примере, возвращается напрямую. В это время HTTP сначала преобразует строку в буферный тип перед передачей и передает ее как двоичные данные. клиенту немного. Однако прямой возврат типа Buffer сократит количество операций преобразования и повысит производительность.
В некоторых веб-приложениях статические данные могут быть заранее преобразованы в буфер для передачи, что может эффективно сократить повторное использование ЦП (повторение строк в операции буфера).
Reference
- узел будет .capable/api/buffer. …
- Подробное объяснение Node.js Buffer
- Do you want a better understanding of Buffer in Node.js? Check this out.
- A cartoon intro to ArrayBuffers and SharedArrayBuffers
- buffer.js v10.x
Приглашаю всех обратить внимание на публичный аккаунт «Nodejs Technology Stack», сканируйте и подписывайтесь на меня!