Javascript ArrayBuffer 到十六进制

IT技术 javascript
2021-02-04 19:33:20

我有一个 Javascript ArrayBuffer,我想将其转换为十六进制字符串。

任何人都知道我可以调用的函数或已经存在的预先编写的函数?

我只能找到数组缓冲区到字符串函数,但我想要数组缓冲区的十六进制转储。

6个回答

function buf2hex(buffer) { // buffer is an ArrayBuffer
  return [...new Uint8Array(buffer)]
      .map(x => x.toString(16).padStart(2, '0'))
      .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. 最后,它获取所有十六进制值并将它们连接成一个字符串。

下面是另一个更容易理解的更长的实现,但本质上做同样的事情:

不太确定这是如何工作的,但似乎与 c# 的输出相同... string myString = BitConverter.ToString(new byte[] {120,144,107});
2021-03-16 19:33:20
@etienne-martin 很好的例子,ES6 有效。我正在尝试你的 ES5 示例,我用 node.js 运行它,然后回答作为 04040404 返回。知道吗????在 ES6 示例中,您有 Array.prototype.map.call,但在 ES5 中没有,应该是这样吗?
2021-03-17 19:33:20
@da_jokker 查看底部的代码片段,它更详细地解释了代码的工作原理。
2021-04-02 19:33:20
嗨,当我运行 ES5 时,它返回 04040404,有什么建议吗?
2021-04-07 19:33:20
@Frxstrem 我认为你可以用“0”而不是“00”来填充,因为最短x.toString(16)的仍然是一个字符
2021-04-09 19:33:20

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

function bufferToHex (buffer) {
    return [...new Uint8Array (buffer)]
        .map (b => b.toString (16).padStart (2, "0"))
        .join ("");
}

这是如何工作的:

  1. AnArray是从Uint8Array保存缓冲区数据的a 创建的这样我们就可以稍后修改数组以保存字符串值。
  2. 所有Array项目都映射到它们的十六进制代码并用0字符填充
  3. 数组被连接成一个完整的字符串。
在某人编辑后,接受的答案代码现在与此相同 =D
2021-03-30 19:33:20
扩展语法[...new Uint8Array(buffer)]可能会稍微快一些(如果其他测试是一个指示)。
2021-04-01 19:33:20

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

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%)

好吧,这是一个令人失望的实验。我写了这个函数,因为我认为它会比 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);
}

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

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

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

第 1 课

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

第 2 课

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

为什么不使用reduce?
2021-03-22 19:33:20
@Griffork您绝对可以使用reduce。事实上,我猜它比我包含的使用 map 的解决方案要好得多。我从未测试过 reduce 但不幸的是我猜它仍然比普通的旧 for 循环慢。根据我的经验,JS 引擎很难优化 map/reduce/forEach/etc 等函数式编程方法。我现在还有其他事情要做,但是如果您测试并发现 reduce 的性能与 for 循环一样好或更好,请告诉我,我将编辑该帖子。
2021-04-11 19:33:20

这是另一个解决方案,它在 Chrome(也可能是节点)上比使用mapand的其他建议快 3 倍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

不错的解决方案!为了与常见的输出兼容,例如 Buffer.from(buffer).toString('hex') 你应该使用小写字符,即 '0123456789abcdef'。
2021-03-27 19:33:20
有趣的做法!+1
2021-04-12 19:33:20

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

const buffer = new Uint8Array([ 4, 8, 12, 16 ]);
console.log(Buffer.from(buffer).toString("hex")); // = 04080c10
非常适用于 Node。遗憾的是,在浏览器中不可用。
2021-03-18 19:33:20
2021 年为我工作
2021-04-13 19:33:20