Автор выбрал Фонд помощи по COVID-19 для получения пожертвования в рамках программы Писать для пожертвований.
Введение
A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer
class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.
Возможно, вы уже использовали буферы неявно, если уже писали код на Node.js. Например, когда вы считываете данные из файла с помощью fs.readFile()
, данные, возвращаемые в обратный вызов или обещание, представляют собой объект буфера . Кроме того, когда в Node.js выполняются HTTP-запросы, они возвращают потоки данных, которые временно сохраняются во внутреннем буфере, когда клиент не может обработать поток сразу целиком.
Буферы полезны, когда вы работаете с двоичными данными, обычно на более низких уровнях сети. Они также дают вам возможность выполнять манипуляции с данными с тонкой настройкой в Node.js.
В этом руководстве вы будете использовать Node.js REPL для выполнения различных примеров работы с буферами, таких как создание буферов, чтение из буферов, запись в буферы, копирование из буферов и использование буферов для преобразования между двоичными и закодированными данными. К концу руководства вы узнаете, как использовать класс Buffer
для работы с двоичными данными.
Необходимые условия
- Вам понадобится установленный Node.js на вашем рабочем компьютере. В этом руководстве используется версия 10.19.0. Чтобы установить ее на macOS или Ubuntu 18.04, следуйте инструкциям в разделе Как установить Node.js и создать локальное рабочее окружение на macOS или разделе Установка с использованием PPA в статье Как установить Node.js на Ubuntu 18.04.
- В этом руководстве вы будете взаимодействовать с буферами в Node.js REPL (Read-Evaluate-Print-Loop). Если вам нужно освежить в памяти, как эффективно использовать Node.js REPL, вы можете прочитать наше руководство по Как использовать Node.js REPL.
- Для этой статьи мы ожидаем, что пользователь будет уверенно владеть основами JavaScript и его типами данных. Вы можете изучить эти основы с помощью нашей серии Как писать на JavaScript.
Шаг 1 — Создание буфера
Этот первый шаг покажет вам два основных способа создания объекта буфера в Node.js.
Чтобы решить, какой метод использовать, вам нужно ответить на вопрос: хотите ли вы создать новый буфер или извлечь буфер из существующих данных? Если вы собираетесь хранить данные в памяти, которые вы еще не получили, вам следует создать новый буфер. В Node.js мы используем функцию alloc()
класса Buffer
для этого.
Давайте откроем REPL Node.js, чтобы убедиться в этом сами. В вашем терминале введите команду node
:
Вы увидите приглашение, начинающееся с >
.
Функция alloc()
принимает размер буфера в качестве своего первого и единственного обязательного аргумента. Размер представляет собой целое число, указывающее, сколько байтов памяти будет использовано объектом буфера. Например, если мы хотим создать буфер размером 1 кБ (килобайт), эквивалентный 1024 байтам, мы введем это в консоли:
Для создания нового буфера мы использовали глобально доступный класс Buffer
, который имеет метод alloc()
. Предоставив 1024
в качестве аргумента для alloc()
, мы создали буфер размером 1 КБ.
По умолчанию, при инициализации буфера с помощью alloc()
, буфер заполняется двоичными нулями в качестве заполнителя для будущих данных. Однако мы можем изменить значение по умолчанию, если захотим. Если мы хотим создать новый буфер с 1
вместо 0
, мы установим второй параметр функции alloc()
— fill
.
В вашем терминале создайте новый буфер при приглашении REPL, заполненный 1
ми:
Мы только что создали новый объект буфера, который ссылается на область в памяти, содержащую 1 КБ 1
ок. Хотя мы ввели целое число, все данные, хранящиеся в буфере, являются двоичными данными.
Двоичные данные могут иметь множество различных форматов. Например, рассмотрим двоичную последовательность, представляющую байт данных: 01110110
. Если бы эта двоичная последовательность представляла строку на английском с использованием стандарта кодирования ASCII, это была бы буква v
. Однако, если наш компьютер обрабатывал изображение, эта двоичная последовательность могла содержать информацию о цвете пикселя.
Компьютер умеет обрабатывать их по-разному, потому что байты кодируются по-разному. Кодирование байтов – это формат байта. Буфер в Node.js по умолчанию использует схему кодирования UTF-8, если он инициализирован строковыми данными. Байт в UTF-8 представляет число, букву (на английском и других языках) или символ. UTF-8 является надмножеством ASCII, американского стандартного кода обмена информацией. ASCII может кодировать байты с прописными и строчными английскими буквами, числами от 0 до 9 и несколькими другими символами, такими как восклицательный знак (!) или знак амперсанда (&).
Если бы мы писали программу, которая могла бы работать только с символами ASCII, мы могли бы изменить кодировку, используемую нашим буфером, с помощью третьего аргумента функции alloc()
– encoding
.
Давайте создадим новый буфер длиной в пять байт и сохраняющий только символы ASCII:
Буфер инициализируется пятью байтами символа a
, используя ASCII-представление.
Примечание: По умолчанию Node.js поддерживает следующие кодировки символов:
- ASCII, представленная как
ascii
- UTF-8, представленная как
utf-8
илиutf8
- UTF-16, представленная как
utf-16le
илиutf16le
- UCS-2, представленный как
ucs-2
илиucs2
- Base64, представленный как
base64
- Шестнадцатеричный, представленный как
hex
- ISO/IEC 8859-1, представленный как
latin1
илиbinary
Все эти значения можно использовать в функциях класса Buffer, принимающих параметр encoding
. Поэтому все эти значения допустимы для метода alloc()
.
До сих пор мы создавали новые буферы с помощью функции alloc()
. Но иногда мы можем захотеть создать буфер из уже существующих данных, таких как строка или массив.
Чтобы создать буфер из существующих данных, мы используем метод from()
. Мы можем использовать эту функцию для создания буферов из:
- Массива целых чисел: Целочисленные значения могут быть между
0
и255
. ArrayBuffer
: Это объект JavaScript, который хранит фиксированную длину байтов.- A string.
- Еще одного буфера.
- Другие объекты JavaScript, которые имеют свойство
Symbol.toPrimitive
. Это свойство указывает JavaScript, как преобразовать объект в примитивный тип данных:boolean
,null
,undefined
,number
,string
илиsymbol
. Вы можете прочитать больше о символах в JavaScript на документации Mozilla: documentation.
Давайте посмотрим, как мы можем создать буфер из строки. Введите это в приглашение Node.js:
Теперь у нас есть объект буфера, созданный из строки My name is Paul
. Давайте создадим новый буфер из другого буфера, который мы ранее создали:
Теперь мы создали новый буфер asciiCopy
, который содержит те же данные, что и asciiBuf
.
Теперь, когда мы познакомились с созданием буферов, мы можем перейти к примерам чтения их данных.
Шаг 2 — Чтение из буфера
Существует множество способов доступа к данным в буфере. Мы можем получить доступ к отдельному байту в буфере, или мы можем извлечь все содержимое.
Чтобы получить доступ к одному байту буфера, мы передаем индекс или расположение байта, который мы хотим. Буферы хранят данные последовательно, как массивы. Они также индексируют свои данные, начиная с 0
. Мы можем использовать нотацию массива на объекте буфера, чтобы получить отдельный байт.
Давайте посмотрим, как это выглядит, создав буфер из строки в REPL:
Теперь давайте прочитаем первый байт буфера:
При нажатии ENTER
, REPL отобразит:
Output72
Целое число 72
соответствует UTF-8 представлению буквы H
.
Примечание: Значения для байтов могут быть числами от 0
до 255
. Байт – это последовательность из 8 бит. Бит бинарен и, следовательно, может иметь только одно из двух значений: 0
или 1
. Если у нас есть последовательность из 8 бит и два возможных значения на бит, то у нас есть максимум 2⁸ возможных значений для байта. Это означает максимум 256 значений. Поскольку мы начинаем считать с нуля, это означает, что нашим наибольшим числом является 255.
Давайте сделаем то же самое для второго байта. Введите следующее в REPL:
REPL возвращает 105
, что представляет собой строчную букву i
.
Наконец, давайте получим третий символ:
Вы увидите 33
, отображенное в REPL, что соответствует !
.
Попробуем извлечь байт из недопустимого индекса:
REPL вернет:
Outputundefined
Это так же, как если бы мы пытались получить доступ к элементу в массиве с некорректным индексом.
Теперь, когда мы видели, как читать отдельные байты буфера, давайте рассмотрим наши варианты для получения всех данных, хранящихся в буфере сразу. Объект буфера поставляется с методами toString()
и toJSON()
, которые возвращают все содержимое буфера в двух различных форматах.
Как следует из названия, метод toString()
преобразует байты буфера в строку и возвращает её пользователю. Если мы используем этот метод с hiBuf
, мы получим строку Hi!
. Давайте попробуем!
Введите в приглашение:
REPL вернет:
Output'Hi!'
Этот буфер был создан из строки. Давайте посмотрим, что произойдет, если мы используем метод toString()
на буфере, который не был создан из строковых данных.
Создадим новый пустой буфер размером 10
байт:
Теперь воспользуемся методом toString()
:
Мы увидим следующий результат:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
Строка \u0000
является Unicode-символом для NULL
. Она соответствует числу 0
. Когда данные буфера не закодированы как строка, метод toString()
возвращает кодировку UTF-8 байтов.
toString()
имеет необязательный параметр, encoding
. Мы можем использовать этот параметр, чтобы изменить кодировку данных буфера, которая будет возвращена.
Например, если вы хотите получить шестнадцатеричное представление для hiBuf
, вы можете ввести следующее в приглашение:
Это выражение приведет к:
Output'486921'
486921
– шестнадцатеричное представление байтов, представляющих строку Hi!
. В Node.js, когда пользователи хотят преобразовать кодировку данных из одной формы в другую, они обычно помещают строку в буфер и вызывают toString()
с нужной кодировкой.
Метод toJSON()
ведет себя по-разному. Независимо от того, был ли буфер создан из строки или нет, он всегда возвращает данные в виде целочисленного представления байта.
Давайте повторно используем буферы hiBuf
и tenZeroes
, чтобы попрактиковаться в использовании toJSON()
. Пригласим к вводу:
REPL вернет:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
У JSON-объекта есть свойство type
, которое всегда будет равно Buffer
. Это позволяет программам отличать эти JSON-объекты от других JSON-объектов.
Свойство data
содержит массив целочисленного представления байтов. Вы могли заметить, что 72
, 105
и 33
соответствуют значениям, которые мы получили, извлекая байты по отдельности.
Давайте попробуем метод toJSON()
с tenZeroes
:
В REPL вы увидите следующее:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
Тип такой же, как было отмечено ранее. Однако данные теперь представляют собой массив с десятью нулями.
Теперь, когда мы рассмотрели основные способы чтения из буфера, давайте посмотрим, как мы можем изменить содержимое буфера.
Шаг 3 — Изменение буфера
Существует множество способов модификации существующего объекта буфера. Подобно чтению, мы можем изменять отдельные байты буфера, используя синтаксис массива. Мы также можем записывать новые данные в буфер, заменяя существующие данные.
Давайте начнем с того, как мы можем изменить отдельные байты буфера. Вспомним нашу переменную буфера hiBuf
, которая содержит строку Hi!
. Давайте изменить каждый байт так, чтобы он содержал вместо этого Hey
.
В REPL давайте сначала попробуем установить второй элемент hiBuf
в e
:
Теперь давайте увидим этот буфер как строку, чтобы убедиться, что он хранит правильные данные. Затем вызовем метод toString()
:
Он будет вычислен как:
Output'H\u0000!'
Мы получили этот странный вывод, потому что буфер может принимать только целочисленное значение. Мы не можем назначить ему букву e
; скорее, мы должны назначить ему число, двоичное представление которого представляет e
:
Теперь, когда мы вызываем метод toString()
:
Мы получаем этот вывод в REPL:
Output'He!'
Чтобы изменить последний символ в буфере, нам нужно установить третий элемент равным целому числу, которое соответствует байту для y
:
Подтвердим, используя метод toString()
еще раз:
Ваш REPL отобразит:
Output'Hey'
Если мы попробуем записать байт, который находится за пределами диапазона буфера, он будет проигнорирован, и содержимое буфера не изменится. Например, давайте попробуем установить несуществующий четвертый элемент буфера равным o
:
Мы можем подтвердить, что буфер не изменился с помощью метода toString()
:
Вывод все еще:
Output'Hey'
Если мы хотим изменить содержимое всего буфера, мы можем использовать метод write()
. Метод write()
принимает строку, которая заменит содержимое буфера.
Давайте используем метод write()
, чтобы изменить содержимое hiBuf
обратно на Hi!
. В вашем оболочке Node.js введите следующую команду:
Метод write()
вернул 3
в REPL. Это потому, что он записал три байта данных. Каждая буква имеет размер одного байта, так как этот буфер использует кодировку UTF-8, в которой каждому символу соответствует один байт. Если бы буфер использовал кодировку UTF-16, где минимальный размер каждого символа составляет два байта, то функция write()
вернула бы 6
.
Теперь проверьте содержимое буфера с помощью toString()
:
REPL выведет:
Output'Hi!'
Это быстрее, чем изменение каждого элемента по одному байту.
Если вы попытаетесь записать больше байтов, чем размер буфера, объект буфера примет только то, что поместится. Для иллюстрации создадим буфер, который хранит три байта:
Теперь попытаемся записать в него Cats
:
Когда вызывается write()
, REPL возвращает 3
, указывая, что в буфер были записаны только три байта. Теперь подтвердите, что буфер содержит первые три байта:
REPL вернет:
Output'Cat'
Функция write()
добавляет байты последовательно, поэтому в буфер были помещены только первые три байта.
В отличие от этого, давайте создадим Буфер
, который содержит четыре байта:
Запишем в него те же данные:
Затем добавим некоторое новое содержимое, занимающее меньше места, чем изначальное:
Поскольку буферы пишут последовательно, начиная с 0
, если мы выведем содержимое буфера:
Мы увидим следующее:
Output'Hits'
Первые два символа перезаписаны, но остальная часть буфера остается нетронутой.
Иногда данные, которые мы хотим поместить в наш существующий буфер, не находятся в строке, а находятся в другом объекте буфера. В таких случаях мы можем использовать функцию copy()
, чтобы изменить содержимое нашего буфера.
Давайте создадим два новых буфера:
Буферы wordsBuf
и catchphraseBuf
содержат строковые данные. Мы хотим изменить catchphraseBuf
, чтобы он содержал Nananana Turtle!
вместо Not sure Turtle!
. Мы используем copy()
, чтобы скопировать Nananana
из wordsBuf
в catchphraseBuf
.
Чтобы скопировать данные из одного буфера в другой, мы будем использовать метод copy()
на буфере, который является источником информации. Следовательно, так как wordsBuf
содержит строковые данные, которые мы хотим скопировать, мы делаем это следующим образом:
Параметр target
в данном случае – это буфер catchphraseBuf
.
Когда мы вводим это в REPL, он возвращает 15
, указывая, что было записано 15 байт. Строка Nananana
использует только 8 байт данных, поэтому мы сразу понимаем, что наша копия не прошла по плану. Используйте метод toString()
, чтобы увидеть содержимое catchphraseBuf
:
REPL возвращает:
Output'Banana Nananana!'
По умолчанию copy()
взял весь контент из wordsBuf
и поместил его в catchphraseBuf
. Нам нужно быть более селективными для достижения нашей цели и скопировать только Nananana
. Давайте перепишем исходное содержимое catchphraseBuf
перед продолжением:
Функция copy()
имеет еще несколько параметров, которые позволяют настраивать, какие данные копируются в другой буфер. Вот список всех параметров этой функции:
target
– Это единственный обязательный параметрcopy()
. Как мы видели из нашего предыдущего использования, это буфер, в который мы хотим скопировать.targetStart
– Это индекс байтов в целевом буфере, с которого мы должны начать копирование. По умолчанию это0
, что означает, что данные копируются начиная с начала буфера.sourceStart
– Это индекс байтов в исходном буфере, с которого мы должны скопировать данные.sourceEnd
– Это индекс байтов в исходном буфере, на котором мы должны прекратить копирование. По умолчанию это длина буфера.
Итак, чтобы скопировать Nananana
из wordsBuf
в catchphraseBuf
, наш target
должен быть catchphraseBuf
, как и раньше. targetStart
должен быть 0
, так как мы хотим, чтобы Nananana
появился в начале catchphraseBuf
. sourceStart
должен быть 7
, так как это индекс, с которого начинается Nananana
в wordsBuf
. sourceEnd
должен оставаться длиной буферов.
На приглашении REPL скопируйте содержимое wordsBuf
так:
REPL подтверждает, что записано 8
байт. Обратите внимание, как свойство wordsBuf.length
используется как значение для параметра sourceEnd
. Как и в случае с массивами, свойство length
дает нам размер буфера.
Теперь давайте посмотрим содержимое catchphraseBuf
:
REPL возвращает:
Output'Nananana Turtle!'
Успех! Мы смогли изменить данные в catchphraseBuf
, скопировав содержимое wordsBuf
.
Вы можете выйти из Node.js REPL, если хотите. Обратите внимание, что все переменные, созданные ранее, больше не будут доступны:
Заключение
В этом руководстве вы узнали, что буферы – это фиксированные выделения в памяти, которые хранят двоичные данные. Сначала вы создали буферы, определив их размер в памяти и инициализировав их существующими данными. Затем вы читали данные из буфера, рассматривая их индивидуальные байты и используя методы toString()
и toJSON()
. Наконец, вы модифицировали данные, хранящиеся в буфере, изменяя его индивидуальные байты и используя методы write()
и copy()
.
Буферы дают вам отличное представление о том, как двоичные данные обрабатываются в Node.js. Теперь, когда вы можете взаимодействовать с буферами, вы можете наблюдать различные способы кодирования символов и их влияние на хранение данных. Например, вы можете создавать буферы из строковых данных, не соответствующих кодировке UTF-8 или ASCII, и наблюдать их разницу в размере. Вы также можете взять буфер с кодировкой UTF-8 и использовать toString()
для преобразования его в другие схемы кодирования.
Чтобы узнать о буферах в Node.js, вы можете прочитать документацию Node.js на объект Buffer
. Если вы хотите продолжить изучение Node.js, вы можете вернуться к серии “Как программировать на Node.js” или просматривать программные проекты и настройки на нашей странице темы Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js