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

Эта функция работает в четыре этапа:

  1. Преобразует буфер в массив.
  2. Для каждого x массив, он преобразует этот элемент в шестнадцатеричную строку (например, 12 становится c).
  3. Затем он берет эту шестнадцатеричную строку и оставляет ее с нулями (например, c становится 0c).
  4. Наконец, он принимает все шестнадцатеричные значения и объединяет их в одну строку.

Ниже приведена еще одна более длинная реализация, которая немного проще для понимания, но по сути делает то же самое:

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 ("");
}

Как это работает:

  1. Array создан из Uint8Array держа данные буфера. Это так, что мы можем изменить массив для хранения строковых значений позже.
  2. Все Array элементы сопоставляются с их шестнадцатеричными кодами и дополняются 0 персонажи.
  3. Массив объединяется в полную строку.

Самый простой способ преобразовать буфер массива в шестнадцатеричный:

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 мс
  • uint8ArrayToHexString280 мс
  • 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 сбрасывает Buffers.

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>

В Node мы можем использовать Buffer.from(unitarray, “hex”)

У меня была такая же задача, вот то, что я нашел лучшим решением

      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'))
Другие вопросы по тегам