Here are several methods for encoding an
ArrayBuffer to hex, in order of speed. All methods were tested in Firefox initially, but afterwards I went and tested in Chrome (V8). In Chrome the methods were mostly in the same order but it did have slight differenences--the important thing is that #1 is the fastest method in all environments by a huge margin.
If you want to see how slow the currently selected answer is, you can go ahead and scroll to the bottom of this list lmao.
Be good boys/girls and use solution #1. It is both the fastest and the best supported. The only faster method of encoding to hex in a browser is writing optimized C code and compiling to Web Assembly.
1. Precomputed Hex Octets w/
for Loop (Fastest/Baseline)
This approach computes the 2-character hex octets for every possible value of an unsigned byte:
[0, 255], and then just maps each value in the
ArrayBuffer through the array of octet strings. Credit to Aaron Watters for the original answer using this method.
const byteToHex = ;
for (let n = 0; n <= 0xff; ++n)
const hexOctet = n.toString(16).padStart(2, "0");
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)
2. Precomputed Hex Octets w/
Array.map (~30% slower)
Same as the above method, where we precompute an array in which the value for each index is the hex string for the index's value, but we use a hack where we call the
map() method with the buffer. This is a more functional approach, but if you really want speed you will always use
for loops rather than ES6 array methods, as all modern JS engines optimize them much better.
IMPORTANT: You cannot use
new Uint8Array(arrayBuffer).map(...). Although
Uint8Array implements the
ArrayLike interface, its
map method will return another
Uint8Array which cannot contain strings (hex octets in our case), hence the
Array prototype hack.
n => byteToHex[n]
3. Precomputed ASCII Character Codes (~230% slower)
Well this was a disappointing experiment. I wrote up this function because I thought it would be even faster than Aaron's precomputed hex octets--boy was I wrong LOL. While Aaron maps entire bytes to their corresponding 2-character hex codes, this solution uses bitshifting to get the hex character for the first 4 bits in each byte and then the one for the last 4 and uses
String.fromCharCode(). Honestly I think
String.fromCharCode() must just be poorly optimized, since it is not used by very many people and is low on browser vendors' lists of priorities.
const asciiCodes = new Uint8Array(
char => char.charCodeAt()
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];
padStart() (~290% slower)
This method maps an array of bytes using the
Number.toString() method to get the hex and then padding the octet with a "0" if necessary via the
String.padStart() is a relative new standard, so you should not use this or method #5 if you are planning on supporting browsers older than 2017 or so or Internet Explorer. TBH if your users are still using IE you should probably just go to their houses at this point and install Chrome/Firefox. Do us all a favor. :^D
n => n.toString(16).padStart(2, "0")
padStart() (~370% slower)
This is the same as #4 but instead of the
Array prototype hack, we create an actual number array from the
Uint8Array and call
map() on that directly. We pay in speed though.
return Array.from(new Uint8Array(arrayBuffer))
.map(n => n.toString(16).padStart(2, "0"))
slice() (~450% slower)
This is the selected answer, do not use this unless you are a typical web developer and performance makes you uneasy (answer #1 is supported by just as many browsers).
n => ("0" + n.toString(16)).slice(-2)