“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情”
Convert hex to base64
解题
我采用 JavaScript 解答,首先我会想,基于 V8 的 JS 引擎有没有已经实现的 API 呢?
很好,我在浏览器中找到了 btoa
和 atob
。
btoa() 函数将一个 JavaScript 字符串作为其参数。而 JavaScript 字符串使用 UTF-16 字符编码表示:在这种编码中,字符串使用一串 16 比特(2 字节)的单元来表示。
不用管,我先试试。
const hexString = '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
const base64String = btoa(hexString)
打印结果为:NDkyNzZkMjA2YjY5NmM2YzY5NmU2NzIwNzk2Zjc1NzIyMDYyNzI2MTY5NmUyMDZjNjk2YjY1MjA2MTIwNzA2ZjY5NzM2ZjZlNmY3NTczMjA2ZDc1NzM2ODcyNmY2ZjZk
和答案差距甚远,这是为什么呢?我注意到题目要求 始终对原始字节进行操作,而不对编码字符串进行操作 。明白了,我不能操作 hexString 这个字符串,我要操作 hexString 这个字符串的字节,把它转换成字节数组,然后再转换成 base64 字符串。于是找到了 ArrayBuffer
缓存区。
ArrayBuffer:它是一个字节数组, 用于表示通用的、固定长度的原始二进制数据缓冲区。这个和题目中的 始终对原始字节进行操作 就很搭。但是不能直接操作 ArrayBuffer,需要通过
DataView
或者类型化数组对象来操作。 更多
const hexString = '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
const buffer = new ArrayBuffer(hexString.length / 2)
const dataView = new DataView(buffer)
// 两个字符为一组,转换成十进制,然后存入缓存区
// 为什么要两个字符为一组呢?因为十六进制的每个字符表示四个二进制位,两个十六进制字符表示八个二进制位,也就是一个字节。这也是为什么 buffer 长度为 hexString.length / 2 的原因
for (let i = 0; i < hexString.length; i += 2) {
dataView.setUint8(i / 2, parseInt(hexString.substring(i, i + 2), 16))
}
// 缓冲区填满后,转换成 base64 字符串
// 首先,缓冲区是无法直接操作的,虽然我们已经填充了数据,但是里面都是数字,而且是十进制的,范围是 0-255,此时我们可以将里面对应的数字转换成对应的字符,然后再转换成 base64 字符串
const unit8Array = new Uint8Array(buffer) // Uint8Array: 8 位无符号整数类型化数组
const btr = ''
unit8Array.forEach((item) => {
btr += String.fromCharCode(item)
})
const base64String = btoa(btr)
输出结果:SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
,和要求一致,芜湖!
优化
我发现,刚才里面绕来绕去,过程是将字符串分割为两个一组,作为16进制转为10进制,查找对应的ascii码,最后转为base64,就不需要 ArrayBuffer
了,直接分割字符串,然后转换就可以了。
const hexString = '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
let str = ''
for (let i = 0; i < hexString.length; i += 2) {
// 一步到位,直接将16进制转为10进制,然后转为ascii码
str += String.fromCharCode(parseInt(hexString.substring(i, i + 2), 16))
}
const base64String = btoa(str)
打印结果:base64String = SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t ,和结果一致,ASCII 码字符串为:I'm killing your brain like a poisonous mushroom
。还是意外之喜,也许这才是理想的过程。
感谢 node 提供的 buffer api
Buffer 对象用于表示固定长度的字节序列。当在 Buffer 和字符串之间进行转换时,可以指定字符编码。 如果未指定字符编码,则默认使用 UTF-8。详细参考:buffer-node
const hexString = '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
function hexToB64(str) {
return Buffer.from(str, 'hex').toString('base64')
}
const res = hexToB64(hexString)
总结
在 JavaScript 中,都不需要知道 base64 是如何对字节进行拼接的,只要找到 runtime 里的API 就能完成,这是出于对底层实现的信任,也是对完成任务的急迫。 其实一开始是先找到 node 的处理,但是发现在浏览器里行不通,而且毫无体验,就继续在浏览器里找找看还有什么方法。(对,water)
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情”