【发布时间】:2016-10-13 21:57:58
【问题描述】:
我有一个 Javascript ArrayBuffer,我想将其转换为十六进制字符串。
有人知道我可以调用的函数或已经存在的预先编写的函数吗?
我只能找到数组缓冲区到字符串函数,但我想要数组缓冲区的十六进制转储。
【问题讨论】:
-
number.toString(16)怎么样
标签: javascript
我有一个 Javascript ArrayBuffer,我想将其转换为十六进制字符串。
有人知道我可以调用的函数或已经存在的预先编写的函数吗?
我只能找到数组缓冲区到字符串函数,但我想要数组缓冲区的十六进制转储。
【问题讨论】:
number.toString(16)怎么样
标签: javascript
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
此功能分四步工作:
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
【讨论】:
x.toString(16)仍然是一个字符
这是一个甜蜜的 ES6 解决方案,使用 padStart 并避免了相当混乱的基于原型调用的已接受答案的解决方案。 实际上也更快。
function bufferToHex (buffer) {
return [...new Uint8Array (buffer)]
.map (b => b.toString (16).padStart (2, "0"))
.join ("");
}
这是如何工作的:
Array 是从保存缓冲区数据的Uint8Array 创建的。这样我们就可以稍后修改数组以保存字符串值。Array 项目都映射到其十六进制代码并用0 字符填充。【讨论】:
[...new Uint8Array(buffer)] 可能会稍微快一些(如果other tests 是一个指示)。
这里有几种将ArrayBuffer 编码为十六进制的方法,按速度排序。所有方法最初都在 Firefox 中进行了测试,但后来我又在 Chrome (V8) 中进行了测试。在 Chrome 中,这些方法的顺序大多相同,但确实存在细微差别——重要的是,#1 是所有环境中最快的方法,相差 巨大。
如果您想查看当前选中的answer 有多慢,您可以继续滚动到此列表的底部。
方法#1(就在这个下方)是我测试的用于编码为十六进制字符串的最快方法。如果出于某些非常充分的理由,您需要支持 IE,您可能需要在预计算十六进制八位字节时将 .padStart 调用替换为方法 #6 中使用的 .slice 技巧,以确保每个八位字节为 2 个字符。
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("");
}
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("");
}
嗯,这是一个令人失望的实验。我写了这个函数,因为我认为它会比 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);
}
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("");
}
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("");
}
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("");
}
预计算的东西有时可以是一种非常有效的内存换速度折衷。理论上,预计算的十六进制八位字节数组可以存储在 1024 个字节中(256 个可能的十六进制值 ⨉ 2 个字符/ value ⨉ 2 个字节/字符,用于大多数/所有浏览器使用的 UTF-16 字符串表示),在现代计算机中 nothing。实际上,其中有更多字节用于存储数组和字符串长度,并且可能是类型信息,因为这是 JavaScript,但是对于 大量 性能改进,内存使用仍然可以忽略不计。
帮助优化编译器。 浏览器的 JavaScript 编译器会定期尝试理解您的代码并将其分解为您的 CPU 执行的可能最快的机器代码。因为 JavaScript 是一种非常动态的语言,这可能很难做到,有时浏览器会放弃并留下各种类型检查,更糟糕的是,因为它无法确定 x 确实是一个字符串或数字,反之亦然。使用诸如内置Array 类的.map 方法之类的现代函数式编程添加可能会给浏览器带来麻烦,因为回调函数可以捕获外部变量并执行各种其他通常会损害性能的事情。 For-loops 是经过充分研究且相对简单的结构,因此浏览器开发人员已经为编译器结合了各种技巧来优化 JavaScript for-loops。保持简单。
【讨论】:
这是另一个解决方案,它在 Chrome(也可能是节点)上比使用 map 和 toString 的其他建议快约 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
【讨论】:
将arraybuffer转换为十六进制的最简单方法:
const buffer = new Uint8Array([ 4, 8, 12, 16 ]);
console.log(Buffer.from(buffer).toString("hex")); // = 04080c10
【讨论】:
以下解决方案使用预先计算的查找表进行前向和后向转换。
// 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 循环,现在它甚至更快。]
【讨论】:
String.fromCharCodes() 而不是 join 字符串数组来加快速度。不过,这纯粹是猜测,我需要测试。
这个灵感来自 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));
}
【讨论】:
.join 更快(不知道是否仍然如此)。
我用它来 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>
【讨论】:
在Node中,我们可以使用Buffer.from(uint8array, "hex")
【讨论】:
uint8array.reduce((a, b) => a + b.toString(16).padStart(2, '0'), '')
令人惊讶的是,使用reduce 而不是map 很重要。这是因为 map 为类型化数组重新实现,为每个元素返回一个类型化数组,而不是一个 uint8。
【讨论】: