Javascript ArrayBuffer для Hex
У меня есть Javascript ArrayBuffer, который я хотел бы преобразовать в шестнадцатеричную строку.
Кто-нибудь знает функцию, которую я могу вызвать, или уже написанную ранее функцию?
Я только смог найти массив буферов для строковых функций, но я хочу вместо этого использовать hexdump буфера массива.
13 ответов
function buf2hex(buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
// EXAMPLE:
const buffer = new Uint8Array([ 4, 8, 12, 16 ]).buffer;
console.log(buf2hex(buffer)); // = 04080c10
Эта функция работает в четыре этапа:
- Преобразует буфер в массив.
- Для каждого
x
массив, он преобразует этот элемент в шестнадцатеричную строку (например,12
становитсяc
). - Затем он берет эту шестнадцатеричную строку и оставляет ее с нулями (например,
c
становится0c
). - Наконец, он принимает все шестнадцатеричные значения и объединяет их в одну строку.
Ниже приведена еще одна более длинная реализация, которая немного проще для понимания, но по сути делает то же самое:
function buf2hex(buffer) { // buffer is an ArrayBuffer
// create a byte array (Uint8Array) that we can use to read the array buffer
const byteArray = new Uint8Array(buffer);
// for each element, we want to get its two-digit hexadecimal representation
const hexParts = [];
for(let i = 0; i < byteArray.length; i++) {
// convert value to hexadecimal
const hex = byteArray[i].toString(16);
// pad with zeros to length 2
const paddedHex = ('00' + hex).slice(-2);
// push to array
hexParts.push(paddedHex);
}
// join all the hex values of the elements into a single string
return hexParts.join('');
}
// EXAMPLE:
const buffer = new Uint8Array([ 4, 8, 12, 16 ]).buffer;
console.log(buf2hex(buffer)); // = 04080c10
Вот несколько методов для кодирования ArrayBuffer
гекс, в порядке скорости. Сначала все методы были протестированы в Firefox, но потом я пошел и протестировал в Chrome (V8). В Chrome методы были в основном в одном и том же порядке, но у него были небольшие различия - важно то, что #1 - самый быстрый метод во всех средах с огромным отрывом.
Если вы хотите увидеть, насколько медленным является выбранный в данный момент ответ, вы можете пойти дальше и прокрутить до конца этого списка lmao.
Внимание Копировщики
Будьте хорошими мальчиками / девочками и используйте решение № 1. Это и самый быстрый и самый лучший поддерживаемый. Единственный более быстрый метод кодирования в hex в браузере - это написание оптимизированного кода на C и компиляция в Web Assembly.
1. Предварительно вычисленные шестнадцатеричные октеты с for
Петля (самая быстрая / базовая)
Этот подход вычисляет 2-символьные шестнадцатеричные октеты для каждого возможного значения байта без знака: [0, 255]
, а затем просто отображает каждое значение в ArrayBuffer
через массив строк октетов. Благодарим Аарона Уоттерса за оригинальный ответ, используя этот метод.
const byteToHex = [];
for (let n = 0; n <= 0xff; ++n)
{
const hexOctet = n.toString(16).padStart(2, "0");
byteToHex.push(hexOctet);
}
function hex(arrayBuffer)
{
const buff = new Uint8Array(arrayBuffer);
const hexOctets = []; // new Array(buff.length) is even faster (preallocates necessary array size), then use hexOctets[i] instead of .push()
for (let i = 0; i < buff.length; ++i)
hexOctets.push(byteToHex[buff[i]]);
return hexOctets.join("");
}
2. Предварительно вычисленные шестнадцатеричные октеты с Array.map
(~30% медленнее)
То же, что и вышеупомянутый метод, где мы предварительно вычисляем массив, в котором значение для каждого индекса является шестнадцатеричной строкой для значения индекса, но мы используем хак, где мы вызываем Array
прототипа map()
метод с буфером. Это более функциональный подход, но если вы действительно хотите скорость, вы всегда будете использовать for
циклы, а не методы массива ES6, поскольку все современные движки JS оптимизируют их намного лучше.
ВАЖНО: Вы не можете использовать
new Uint8Array(arrayBuffer).map(...)
, ХотяUint8Array
реализуетArrayLike
интерфейс, егоmap
метод вернет другойUint8Array
который не может содержать строки (шестнадцатеричные октеты в нашем случае), следовательно,Array
прототип взломать.
function hex(arrayBuffer)
{
return Array.prototype.map.call(
new Uint8Array(arrayBuffer),
n => byteToHex[n]
).join("");
}
3. Предварительно вычисленные коды символов ASCII (~230% медленнее)
Ну, это был неутешительный эксперимент. Я написал эту функцию, потому что думал, что она будет даже быстрее, чем предварительно вычисленные шестнадцатеричные октеты Аарона - парень, я ошибся, LOL. В то время как Аарон отображает целые байты в соответствующие им 2-символьные шестнадцатеричные коды, это решение использует битовую сдвиг, чтобы получить шестнадцатеричный символ для первых 4 битов в каждом байте, а затем один для последних 4, и использует String.fromCharCode()
, Если честно думаю String.fromCharCode()
просто должен быть плохо оптимизирован, так как он не используется очень многими людьми и находится в списке приоритетов производителей браузеров.
const asciiCodes = new Uint8Array(
Array.prototype.map.call(
"0123456789abcdef",
char => char.charCodeAt()
)
);
function hex(arrayBuffer)
{
const buff = new Uint8Array(buff);
const charCodes = new Uint8Array(buff.length * 2);
for (let i = 0; i < buff.length; ++i)
{
charCodes[i * 2] = asciiCodes[buff[i] >>> 4];
charCodes[i * 2 + 1] = asciiCodes[buff[i] & 0xf];
}
return String.fromCharCode(...charCodes);
}
4. Array.prototype.map()
ж / padStart()
(~ 290% медленнее)
Этот метод отображает массив байтов, используя Number.toString()
метод, чтобы получить гекс, а затем заполнить октет "0", если необходимо, через String.padStart()
метод.
ВАЖНЫЙ:
String.padStart()
это относительно новый стандарт, поэтому вам не следует использовать этот или метод № 5, если вы планируете поддерживать браузеры старше 2017 года или около того или Internet Explorer. TBH, если ваши пользователи все еще используют IE, вам, вероятно, следует просто пойти к ним домой и установить Chrome/Firefox. Сделайте нам всем одолжение.: ^ D
function hex(arrayBuffer)
{
return Array.prototype.map.call(
new Uint8Array(arrayBuffer),
n => n.toString(16).padStart(2, "0")
).join("");
}
5. Array.from().map()
ж / padStart()
(~ 370% медленнее)
Это то же самое, что и № 4, но вместо Array
взлом прототипа, мы создаем массив фактических чисел из Uint8Array
и позвонить map()
на это напрямую. Мы платим в скорости, хотя.
function hex(arrayBuffer)
{
return Array.from(new Uint8Array(arrayBuffer))
.map(n => n.toString(16).padStart(2, "0"))
.join("");
}
6. Array.prototype.map()
ж / slice()
(~ 450% медленнее)
Это выбранный ответ, не используйте его, если вы не являетесь типичным веб-разработчиком, и производительность вас не устраивает (ответ № 1 поддерживается столь же многими браузерами).
function hex(arrayBuffer)
{
return Array.prototype.map.call(
new Uint8Array(arrayBuffer),
n => ("0" + n.toString(16)).slice(-2)
).join("");
}
Вот сладкое решение ES6, используя padStart
и избегая довольно запутанного решения принятого ответа на основе вызова прототипа. Это на самом деле быстрее, а также.
function bufferToHex (buffer) {
return Array
.from (new Uint8Array (buffer))
.map (b => b.toString (16).padStart (2, "0"))
.join ("");
}
Как это работает:
Array
создан изUint8Array
держа данные буфера. Это так, что мы можем изменить массив для хранения строковых значений позже.- Все
Array
элементы сопоставляются с их шестнадцатеричными кодами и дополняются0
персонажи. - Массив объединяется в полную строку.
Самый простой способ преобразовать буфер массива в шестнадцатеричный:
const buffer = new Uint8Array([ 4, 8, 12, 16 ]);
console.log(Buffer.from(buffer).toString("hex")); // = 04080c10
Вот еще одно решение, которое в Chrome (и, вероятно, тоже в узле) примерно в 3 раза быстрее, чем другие предложения, использующие map
а также toString
:
function bufferToHex(buffer) {
var s = '', h = '0123456789ABCDEF';
(new Uint8Array(buffer)).forEach((v) => { s += h[v >> 4] + h[v & 15]; });
return s;
}
Дополнительный бонус: вы можете легко выбрать прописные / строчные буквы.
Смотрите скамейку здесь: http://jsben.ch/Vjx2V
uint8array.reduce((a, b) => a + b.toString(16).padStart(2, '0'), '')
Удивительно, но важно использовать
reduce
вместо . Это потому что
map
повторно реализован для типизированных массивов, чтобы возвращать типизированный массив для каждого элемента вместо uint8.
Следующее решение использует предварительно вычисленные таблицы поиска для прямого и обратного преобразования.
// look up tables
var to_hex_array = [];
var to_byte_map = {};
for (var ord=0; ord<=0xff; ord++) {
var s = ord.toString(16);
if (s.length < 2) {
s = "0" + s;
}
to_hex_array.push(s);
to_byte_map[s] = ord;
}
// converter using lookups
function bufferToHex2(buffer) {
var hex_array = [];
//(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) });
for (var i=0; i<buffer.length; i++) {
hex_array.push(to_hex_array[buffer[i]]);
}
return hex_array.join('')
}
// reverse conversion using lookups
function hexToBuffer(s) {
var length2 = s.length;
if ((length2 % 2) != 0) {
throw "hex string must have length a multiple of 2";
}
var length = length2 / 2;
var result = new Uint8Array(length);
for (var i=0; i<length; i++) {
var i2 = i * 2;
var b = s.substring(i2, i2 + 2);
result[i] = to_byte_map[b];
}
return result;
}
Это решение быстрее, чем победитель предыдущего теста: http://jsben.ch/owCk5 протестированный в Chrome и Firefox на ноутбуке Mac. Также см. Контрольный код для функции проверки теста.
[edit: я изменяю forEach на цикл for, и теперь он стал еще быстрее.]
Если вы обнаружите это и вам нужно кодировать / декодировать еще быстрее и потенциально уменьшить объем необходимой памяти, то уже предоставленные ответы могут сработать для вас.
Он использует , который присутствует в любом браузере (https://caniuse.com/textencoder) и в nodejs, для объединения результирующей шестнадцатеричной строки или для получения шестнадцатеричных кодов.
В nodejs вы должны использовать уже предоставленную опцию следующим образом:
function nodeEncode(arr: Uint8Array) {
return Buffer.from(arr).toString('hex');
}
function nodeDecode(hexString: string) {
return Uint8Array.from(Buffer.from(hexString, 'hex'));
}
Но в среде браузера вы можете использоватьTextEncoder
вот так:
const nibbleIntegerToHexCharCode = new TextEncoder().encode("0123456789abcdef");
function uint8ArrayToHexString(input: Uint8Array) {
const output = new Uint8Array(input.length * 2);
for (let i = 0; i < input.length; i++) {
const v = input[i];
output[i * 2 + 0] = nibbleIntegerToHexCharCode[(v & 0xf0) >> 4];
output[i * 2 + 1] = nibbleIntegerToHexCharCode[(v & 0x0f)];
}
return new TextDecoder().decode(output);
}
const charCodeToNibbleInteger = new Uint8Array(0xff + 1);
for (let i = 0; i < charCodeToNibbleInteger.length; i++)
charCodeToNibbleInteger[i] = nibbleIntegerToHexCharCode.findIndex(v => v == i);
function hexStringToUInt8Array(input: string) {
const encodedInput = new TextEncoder().encode(input);
const output = new Uint8Array(encodedInput.length / 2);
for (let i = 0; i < output.length; i++) {
const upper = charCodeToNibbleInteger[encodedInput[i * 2 + 0]] << 4;
const lower = charCodeToNibbleInteger[encodedInput[i * 2 + 1]];
output[i] = upper + lower;
}
return output;
}
Вывод функции функция и функция идентичны, но время их вычисления различается.
Для 22 МБ UInt8Array:
-
nodeEncoder
занимает 70 мс -
uint8ArrayToHexString
280 мс -
hex
занимает 1400 мс
Также может быть разительная разница в объеме используемой памяти.
Этот вариант вдохновлен №1 Сэма Клауса, который действительно является самым быстрым методом здесь. Тем не менее, я обнаружил, что использование простой конкатенации строк вместо использования массива в качестве строкового буфера еще быстрее! По крайней мере, это есть в Chrome. (это V8, который в наши дни есть почти в каждом браузере и NodeJS)
const len = 0x100, byteToHex = new Array(len), char = String.fromCharCode;
let n = 0;
for (; n < 0x0a; ++n) byteToHex[n] = '0' + n;
for (; n < 0x10; ++n) byteToHex[n] = '0' + char(n + 87);
for (; n < len; ++n) byteToHex[n] = n.toString(16);
function byteArrayToHex(byteArray) {
const l = byteArray.length;
let hex = '';
for (let i = 0; i < l; ++i) hex += byteToHex[byteArray[i] % len];
return hex;
}
function bufferToHex(arrayBuffer) {
return byteArrayToHex(new Uint8Array(arrayBuffer));
}
Я использую это для hexdump ArrayBuffer
так же, как Node сбрасывает Buffer
s.
function pad(n: string, width: number, z = '0') {
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
function hexdump(buf: ArrayBuffer) {
let view = new Uint8Array(buf);
let hex = Array.from(view).map(v => this.pad(v.toString(16), 2));
return `<Buffer ${hex.join(" ")}>`;
}
Пример (с транспонированной версией js):
const buffer = new Uint8Array([ 4, 8, 12, 16 ]).buffer;
console.log(hexdump(buffer)); // <Buffer 04 08 0c 10>
У меня была такая же задача, вот то, что я нашел лучшим решением
const buff = new Uint8Array(arrayBuffer);
const buffLen = buff.length;
const hex = new Array(buffLen);
for(let i=0; i<buffLen; i++){
hex[i] = ("0"+ buff[i].toString(16)).slice(-2);
}
console.log(hex);
Вот простая реализация, идущая в обе стороны:
const fromHexString = hexString =>
new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
const toHexString = buffer =>
new Uint8Array(buffer).reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
console.log(toHexString(new Uint8Array([1, 2, 42, 100, 101, 102, 255]).buffer))
console.log(fromHexString('01022a646566ff'))