【问题标题】:Javascript ArrayBuffer to HexJavascript ArrayBuffer 到十六进制
【发布时间】:2016-10-13 21:57:58
【问题描述】:

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

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

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

【问题讨论】:

  • number.toString(16)怎么样

标签: javascript


【解决方案1】:

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. 最后,它采用所有十六进制值并将它们连接成一个字符串。

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

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

【讨论】:

  • 不太确定这是如何工作的,但似乎与 c# 的输出匹配 ... string myString = BitConverter.ToString(new byte[] {120,144,107});
  • @da_jokker 见底部的sn-p代码,更详细的解释了代码的工作原理。
  • @etienne-martin 很好的例子,ES6 有效。我正在尝试您的 ES5 示例,我使用 node.js 运行它并返回 04040404。任何想法?在 ES6 示例中,您有 Array.prototype.map.call,但在 ES5 中没有,应该是这样吗?
  • 嗨,当我运行 ES5 时,它返回为 04040404,有什么建议吗?
  • @Frxstrem 我认为你可以用'0'而不是'00'来填充,因为最短的x.toString(16)仍然是一个字符
【解决方案2】:

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

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

这是如何工作的:

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

【讨论】:

  • 传播语法[...new Uint8Array(buffer)] 可能会稍微快一些(如果other tests 是一个指示)。
  • 经过某人的编辑后,接受的答案代码现在与此相同 =D
【解决方案3】:

这里有几种将ArrayBuffer 编码为十六进制的方法,按速度排序。所有方法最初都在 Firefox 中进行了测试,但后来我又在 Chrome (V8) 中进行了测试。在 Chrome 中,这些方法的顺序大多相同,但确实存在细微差别——重要的是,#1 是所有环境中最快的方法,相差 巨大

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

TL;DR

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

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

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

注意:as mentioned by Cref,您可以通过使用循环将十六进制八位字节连接成一个大字符串来提高 V8(Chromium/Chrome/Edge/Brave/etc.)的性能你去然后在循环之后返回字符串。 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%)

与上面的方法相同,我们预先计算一个数组,其中每个索引的值是索引值的十六进制字符串,但是我们使用了一个 hack,我们使用缓冲区调用 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,我们从Uint8Array 创建一个实际的数字数组并直接调用map()。不过,我们按速度付费。

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 个字符/ value ⨉ 2 个字节/字符,用于大多数/所有浏览器使用的 UTF-16 字符串表示),在现代计算机中 nothing。实际上,其中有更多字节用于存储数组和字符串长度,并且可能是类型信息,因为这是 JavaScript,但是对于 大量 性能改进,内存使用仍然可以忽略不计。

第 2 课

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

【讨论】:

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

这是另一个解决方案,它在 Chrome(也可能是节点)上比使用 maptoString 的其他建议快约 3 倍:

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

【讨论】:

  • 有趣的方法! +1
  • 不错的解决方案!与常见的输出兼容,例如Buffer.from(buffer).toString('hex') 你应该使用小写字符,即'0123456789abcdef'。
【解决方案5】:

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

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

【讨论】:

  • 非常适合 Node.js。很遗憾,在浏览器中不可用。
  • 2021 年为我工作
  • 浏览器有buffer polyfill来支持node API,应该可以解决问题
【解决方案6】:

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

// 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 在 Mac 笔记本电脑上的 Chrome 和 Firefox 中进行了测试。另请参阅测试验证功能的基准代码。

[编辑:我将 forEach 更改为 for 循环,现在它甚至更快。]

【讨论】:

  • 这应该是公认的答案,因为它肯定是最快的。我不确定,但您甚至可以通过映射到 ASCII 字符代码并使用 String.fromCharCodes() 而不是 join 字符串数组来加快速度。不过,这纯粹是猜测,我需要测试。
  • ^^^ 我去测试了到 ASCII 码点的映射(使用 Uint8Array 并在优化方面进行了整整 9 码)比这个页面上的所有方法都慢得多,而且我测试的更多。让我感到惊讶..
【解决方案7】:

这个灵感来自 Sam Claus 的 #1,这确实是这里最快的方法。不过,我发现使用纯字符串连接而不是使用数组作为字符串缓冲区甚至更快!至少它在 Chrome 上。 (现在几乎所有浏览器和 NodeJS 都是 V8)

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

【讨论】:

  • 谢谢,我应该提到这一点。我在 Chrome 上得到了相同的结果,但值得注意的是,在 Firefox 上,带有字符串数组的.join 更快(不知道是否仍然如此)。
【解决方案8】:

我用它来 hexdump ArrayBuffers,就像 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(" ")}>`;
}

Example(带有转译的js版本):

const buffer = new Uint8Array([ 4, 8, 12, 16 ]).buffer;
console.log(hexdump(buffer)); // <Buffer 04 08 0c 10>

【讨论】:

  • 是 TypeScript 吗? ://
  • 是的,但是只要去掉类型注释就又是javascript了
【解决方案9】:

在Node中,我们可以使用Buffer.from(uint8array, "hex")

【讨论】:

    【解决方案10】:
    uint8array.reduce((a, b) => a + b.toString(16).padStart(2, '0'), '')
    

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

    【讨论】:

      猜你喜欢
      • 2016-01-05
      • 2016-08-29
      • 2011-12-09
      • 1970-01-01
      • 1970-01-01
      • 2018-07-26
      • 2014-08-24
      • 2017-05-29
      相关资源
      最近更新 更多