Javascript ArrayBuffer到Hex

d12*_*123 21 javascript

我有一个Javascript ArrayBuffer,我希望将其转换为十六进制字符串.

任何人都知道我可以调用的功能或预先写好的功能吗?

我只能找到arraybuffer到字符串函数,但我想要数组缓冲区的hexdump.

Frx*_*rem 50

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
Run Code Online (Sandbox Code Playgroud)

此功能分四步进行:

  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
Run Code Online (Sandbox Code Playgroud)

  • 你能用ecmascript 5写你的代码吗? (2认同)
  • @Frxstrem 我认为你可以用 '0' 而不是 '00' 填充,因为最短的 `x.toString(16)` 仍然是一个字符 (2认同)

Sam*_*aus 21

以下是ArrayBuffer按速度顺序将 an 编码为十六进制的几种方法。所有方法最初都在 Firefox 中进行了测试,但后来我在 Chrome (V8) 中进行了测试。在Chrome中的方法大多以相同的顺序,但它确实有轻微的differenences -重要的是,#1是由在所有环境中的最快方法巨额保证金。

如果您想查看当前选择的答案有多慢,您可以继续并滚动到此列表的底部。

TL; 博士

方法#1(就在此之下)是我测试过的最快的编码为十六进制字符串的方法。如果出于某些非常好的原因,您需要支持 IE,则在预计算十六进制八位字节以确保每个八位字节为 2 个字符时,您可能需要将.padStart调用替换为.slice方法 #6 中使用的技巧。

1. 带for循环的预计算十六进制八位字节(最快/基线)

此方法为无符号字节的每个可能值计算 2 个字符的十六进制八位字节:[0, 255],然后仅将 中的每个值映射ArrayBuffer到八位字节字符串数组。使用此方法的原始答案归功于 Aaron Watters 。

注意: 正如 Cref 所提到的,通过使用循环将十六进制八位字节连接成一个大字符串,然后在之后返回字符串,您可能会在 V8(Chromium/Chrome/Edge/Brave/等)中获得性能提升环形。V8 似乎很好地优化了字符串连接,而 Firefox 在构建数组然后.join在最后将其转换为字符串时表现更好,就像我在下面的代码中所做的那样。这可能是一个微优化,但会随着优化 JS 编译器的奇思妙想而改变。

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("");
}
Run Code Online (Sandbox Code Playgroud)

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("");
}
Run Code Online (Sandbox Code Playgroud)

3. 预计算的 ASCII 字符代码(慢约 230%)

嗯,这是一个令人失望的实验。我写了这个函数,因为我认为它会比 Aaron 预先计算的十六进制八位字节更快——我错了,哈哈。虽然 Aaron 将整个字节映射到它们相应的 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(arrayBuffer);
    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);
}
Run Code Online (Sandbox Code Playgroud)

4. Array.prototype.map()w/ padStart()(慢~290%)

此方法使用该方法映射一个字节数组Number.toString()以获取十六进制,然后在必要时通过该String.padStart()方法用“0”填充八位字节。

重要提示: String.padStart()是一个相对较新的标准,因此如果您计划支持早于 2017 年左右的浏览器或 Internet Explorer,则不应使用此方法或方法 #5。TBH 如果您的用户仍在使用 IE,那么此时您可能应该去他们家安装 Chrome/Firefox。帮我们一个忙。:^D

function hex(arrayBuffer)
{
    return Array.prototype.map.call(
        new Uint8Array(arrayBuffer),
        n => n.toString(16).padStart(2, "0")
    ).join("");
}
Run Code Online (Sandbox Code Playgroud)

5. Array.from().map()w/ padStart()(慢~370%)

这与 #4 相同,但我们不是Array原型 hack,而是从 中创建一个实际的数字数组Uint8Arraymap()直接调用它。不过我们按速度付款。

function hex(arrayBuffer)
{
    return Array.from(new Uint8Array(arrayBuffer))
        .map(n => n.toString(16).padStart(2, "0"))
        .join("");
}
Run Code Online (Sandbox Code Playgroud)

6. Array.prototype.map()w/ slice()(慢~450%)

这是选定的答案,除非您是典型的 Web 开发人员并且性能让您感到不安,否则不要使用它(答案 #1 也被许多浏览器支持)。

function hex(arrayBuffer)
{
    return Array.prototype.map.call(
        new Uint8Array(arrayBuffer),
        n => ("0" + n.toString(16)).slice(-2)
    ).join("");
}
Run Code Online (Sandbox Code Playgroud)

第 1 课

有时,预先计算的东西可能是一种非常有效的内存与速度的权衡。从理论上说,预先计算的十六进制的八位字节的阵列可以被存储在刚刚1024个字节(256个可能的十六进制值?2个字符/值?2个字节/字用于通过多数/所有浏览器所使用的UTF-16字符串表示),这是没有在现代计算机。实际上有更多的字节用于存储数组和字符串长度以及类型信息,因为这是 JavaScript,但内存使用量仍然可以忽略不计,以实现大规模的性能改进。

第 2 课

帮助优化编译器。浏览器的 JavaScript 编译器会定期尝试理解您的代码并将其分解为尽可能快的机器代码,以便您的 CPU 执行。因为 JavaScript 是一种非常动态的语言,所以这可能很难做到,有时浏览器会放弃并留下各种类型检查,更糟糕的是在幕后,因为它无法确定x确实是一个字符串或数,反之亦然。使用现代函数式编程添加,如.map内置的方法Arrayclass 可能会给浏览器带来麻烦,因为回调函数可以捕获外部变量并执行各种经常损害性能的其他事情。For 循环经过充分研究且结构相对简单,因此浏览器开发人员为编译器整合了各种技巧来优化您的 JavaScript for 循环。把事情简单化。


Nik*_*xDa 16

这是一个甜蜜的ES6解决方案,使用padStart并避免了接受的答案相当混乱的基于原型调用的解决方案.它实际上也更快.

function bufferToHex (buffer) {
    return Array
        .from (new Uint8Array (buffer))
        .map (b => b.toString (16).padStart (2, "0"))
        .join ("");
}
Run Code Online (Sandbox Code Playgroud)

这是如何工作的:

  1. 通过保存缓冲区数据Array创建An Uint8Array.这样我们就可以修改数组以保存字符串值.
  2. 所有Array项目都映射到十六进制代码并用0字符填充.
  3. 该数组连接成一个完整的字符串.

  • 经过某人的编辑后,接受的答案代码现在与此相同=D (4认同)
  • 传播语法“ [... new Uint8Array(buffer)]”可能会稍快一些(如果指示[[other tests](https://jsperf.com/set-iterator-vs-foreach/4))。 (2认同)

sht*_*se8 15

将 arraybuffer 转换为十六进制的最简单方法:

const buffer = new Uint8Array([ 4, 8, 12, 16 ]);
console.log(Buffer.from(buffer).toString("hex")); // = 04080c10
Run Code Online (Sandbox Code Playgroud)

要在 Broswer 中支持 Buffer,请检查: https: //github.com/feross/buffer

  • 非常适合 Node.js。遗憾的是,在浏览器中不可用。 (13认同)

Zun*_*Tzu 12

uint8array.reduce((a, b) => a + b.toString(16).padStart(2, '0'), '')
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,使用reduce代替很重要map。这是因为map类型化数组被重新实现,为每个元素返回一个类型化数组,而不是 uint8。


dim*_*dim 7

这是另一种解决方案,在Chrome(可能还有节点)上,其速度比使用map和的其他建议快3倍toString

function bufferToHex(buffer) {
    var s = '', h = '0123456789ABCDEF';
    (new Uint8Array(buffer)).forEach((v) => { s += h[v >> 4] + h[v & 15]; });
    return s;
}
Run Code Online (Sandbox Code Playgroud)

额外的好处:您可以轻松选择大写/小写输出。

在这里查看工作台:http : //jsben.ch/Vjx2V


Aar*_*ers 5

以下解决方案使用预先计算的查找表进行前向和后向转换。

// 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;
}
Run Code Online (Sandbox Code Playgroud)

该解决方案比上一个基准测试的获胜者更快: http://jsben.ch/owCk5在 Mac 笔记本电脑上的 Chrome 和 Firefox 中进行了测试。另请参阅测试验证函数的基准代码。

[编辑:我将 forEach 更改为 for 循环,现在速度更快了。]